线性表
定义:
由n个特性相同 数据元素(即可有很多数据项)构成的有限序列,同时相邻数据元素之间存在 序偶 关系。
线性表中元素个数就是 表长 。
特点:
·存在唯一一个被称为 “第一个” 的数据元素(线性起点、起始结点);
·存在唯一一个被称为 “最后一个” 的数据元素(线性终点、终端结点);
·除第一个以外,结构中的每个数据元素均 只有一个前驱 ;
·除最后一个外,结构中的每个数据元素均 只有一个后继 。
顺序表示和实现
线性表的顺序表示指的是用一组地址连续的存储单元一次存储线性表的数据元素;
特点:
·数据元素在逻辑上相邻;
·数据元素也在物理次序上相邻(即在内存里也是连续存储的);
·可以理解为 数组 。
存取方式:
随机存取。
几个基本操作(伪码):
·类型定义:
1 #define MAXSIZE 100 2 typedef strut 3 { 4 ElemType *elem; 5 int length; 6 }SqList;
·初始化:
1 Status InitList(SqList &L) 2 { 3 L.elem = new ElemType[MAXSIZE]; 4 if(!L.elem) exit(OVERFLOW); 5 L.length = 0; 6 return OK; 7 }
·取值(时间复杂度O(1)):
1 Status GetElem(SqList L, int i, ElemType &e) 2 { 3 if(i < 1 || i > L.length) return ERROR; 4 e = L.elem[i - 1]; 5 return OK; 6 }
·查找(时间复杂度O(n)):
1 int LocateElem(SqList L, ElemType e) 2 { 3 for(i = 0; i < L.length; i++) 4 if(L.elem[i] == e) return i + 1; 5 return 0; 6 }
·插入(时间复杂度O(n)):
1 Status ListInsert(SqList &L, int i, ElemType e) 2 { 3 if((i < 1) || (i > L.length + 1)) return ERROR; 4 if(L.length == MAXSIZE) return ERROR; 5 for(j = L.length - 1; j >= i - 1; j--) 6 L.elem[j + 1] = L.elem[j]; 7 L.elem[i - 1] = e; 8 ++L.length; 9 return OK; 10 }
·删除(时间复杂度O(n)):
1 Status ListDelete(SqList &L, int i) 2 { 3 if((i < 1) || (i > L.length)) return ERROR; 4 for(j = i; j <= L.length - 1; j++) 5 L.elem[j - 1] = L.elem[j]; 6 -- L.length; 7 return OK; 8 }
链式表示和实现
线性表的链式表示指的是用一组任意的存储单元存储线性表的数据元素;
特点:
·存储单元可以连续也可以不连续;
·也就是以前学的链表。
存取方式:
顺序存取(必须从头指针出发)。
几个基本操作(伪码):
·类型定义:
1 typedef struct LNode 2 { 3 ElemType data; 4 struct LNode *next; 5 }LNode, *LinkList;
关于链表的一些特别的地方:
·data 是 数据域 ,next 是 指针域 ;
·LinkList 和 LNode* 是等价的,但是习惯性的用 LinkList 定义单链表,用 LNode* 定义指向单链表中任意结点的指针变量;
·首元结点:链表中存储第一个数据元素的结点;
·头结点:可有可无(习惯上有),可以不存储信息,头指针连接,其指针域指向首元结点;
·头指针:指向链表中第一个结点的指针,即有头结点就指向头结点,无头结点就指向首元结点。
·初始化:
1 Status InitList(ListList &L) 2 { 3 L = new LNode; 4 L -> next = NULL; 5 return OK; 6 }
·取值:
1 Status GetElem(LinkList l, int i, ElemType &e) 2 { 3 p = L -> next; 4 j = 1; 5 while(p && j < i) 6 { 7 p = p -> next; 8 ++j; 9 } 10 if(!p || j > i) return EEOR; 11 e = p -> data; 12 return OK; 13 }
·查找:
1 LNode *LocateElem(LinkList L, ElemType e) 2 { 3 p = L -> next; 4 while(p && p -> data != e) 5 p = p -> next; 6 return p; 7 }
·插入:
1 Status LinstInsert(LinkList &L, int i, ElemType e) 2 { 3 p = L; 4 j = 0; 5 while(p && (j < i - 1)) 6 { 7 p = p -> next; 8 ++j; 9 } 10 if(!p || j > i - 1) return ERROR; 11 s = new LNode; 12 s -> data = e; 13 s -> next = p -> next; 14 p -> next = s; 15 return OK; 16 }
·删除:
1 Status ListDelete(LinkList &L, int i) 2 { 3 p = L; 4 j = 0; 5 whil((P ->next) && (j < i - 1)) 6 { 7 p = p -> next; 8 ++j; 9 } 10 if(!(p -> next) || (j > i - 1)) return ERROR; 11 q = p -> next; 12 p -> next = q -> next; 13 deletd q; 14 return OK; 15 }
·前插法创建单链表:
1 void CreateList_H(LinkList &L, int n) 2 { 3 L = new LNode; 4 L -> next = NULL; 5 for(i = 0; i < n; ++i) 6 { 7 p = new LNode; 8 cin >> p -> data; 9 p -> next = L -> next; 10 L -> next = p; 11 } 12 }
·后插法创建单链表:
1 void CreateList_R(LinkList &L, int n) 2 { 3 L = new LNode; 4 L -> next = NULL; 5 r = L; 6 for(i = 0; i < n; ++i) 7 { 8 p = new LNode; 9 cin >> p -> data; 10 p -> next = NULL; 11 r -> next = p; 12 r =p; 13 } 14 }
顺序表和单链表对比
顺序表 | 链表 | |
存储空间 | 预先分配,会导致空间闲置或溢出现象 | 动态分配,不会出现存储空间闲置或溢出现象 |
存储密度 | 不用为表示结点间的逻辑关系而增加额外的存储开销,存储密度等于1 | 需要借助指针来体现元素间的逻辑关系,存储密度小于1 |
存取元素 | 随机存取,按位置访问元素的时间复杂度为O(1) | 顺序存取,按位置访问元素的时间复杂度为O(n) |
插入、删除 | 平均移动约表中一般元素,时间复杂度为O(n) | 不需要移动元素,确定插入、删除位置后,时间复杂度为O(1) |
适用情况 |
①表长变化不大,且能事先确定变化范围; ②很少进行插入或者删除操作,经常按元素位置序号访问数据元素 |
①长度变化较大 ②频繁进行插入或者删除操作 |
循环链表和双向链表
这两种不常用就不详细总结了。
循环链表:
特点:表中最后一个结点的指针与指向头结点,整个链形成一个环。
头指针:{ a1: O(1) }
{ an: O(n) }
尾指针:{ a1: R -> next -> next O(1) }
{ an: R O(1) }
由上可得,某些情况下循环链表中设立尾指针能简化操作。
双向链表:
特点:一个结点有两个指针域,一个指向直接后继,一个指向直接前驱。
克服了单链表的单向性,用空间换时间效率。
原文地址:https://www.cnblogs.com/wasi-991017/p/11574856.html