线性表-Linear 线性表的定义和操作 线性表的定义线性表是具有相同 的数据类型的 n ( n ≥ 0 ) n(n \ge 0) n ( n ≥ 0 ) 个 有限序列
L = ( a 1 , . . . . a n ) L = (a_{1},....a_{n}) L = ( a 1 , . . . . a n )
其中 a 1 a_{1} a 1 是唯一的“第一个”数据元素,又称表头元素 , a n a_{n} a n 是唯一的“最后一个”数据元素,又称表尾元素 。除了表头元素 ,每个元素都有唯一的前驱。除了表尾元素 ,每一个元素都有唯一的后驱。以上就是线性表的逻辑特性 。由此得到线性表的特点如下:
表中元素的个数有限 表中元素具有逻辑上的顺序性 表中元素都是数据元素,每个元素都是单个元素 表中元素数据类型都相同,意味着每一个元素都占有相同大小的存储空间 表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑表示什么内容 👿线性表是一种逻辑结构,表示元素之间一对一的相邻关系。顺序表和链表是指存储结构,是不同的概念。顺序表强调了内存上的关系。
线性表的基本操作1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void InitList (auto &L) ;int Length (auto L) ;void LocateElem (auto L,auto e) ;auto GetElem (auto L,auto i) ;auto ListInsert (auto &L,auto i,auto e) ;auto ListDelete (auto &L,auto i,auto &e) ;void PrintList (auto L) ;void Empty (auto L) ;void DestroyList (auto &L) ;
🍨对于线性表的各个类型的优势缺点,可以从 查找 和 增加删除 几个角度来分析。
顺序表线性表的 顺序存储 又称顺序表。他是用一组地址连续的存储单元 依次 存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。第 1 1 1 个元素存储在线性表的起始位置,第 i i i 个元素的存储位置后紧接着就是第 i + 1 i+1 i + 1 个元素,称 i i i 个元素 a i a_{i} a i 为在线性表中的 位序 。因此,顺序表的特点是表中元素的逻辑顺序与其存储物理顺序相同。
L o c ( i ) = L o c ( 0 ) + s i z e o f ( E l e m T y p e ) ∗ i Loc(i) = Loc(0) + sizeof(ElemType)*i L o c ( i ) = L o c ( 0 ) + s i z e o f ( E l e m T y p e ) ∗ i
🤖 线性表中的元素的位序是从 1 开始的,而数组中元素的位序是从 0 开始的
一维数组可以是静态分配 的,也可以是动态分配 的。
静态分配 的时候,由于数组大小和空间事先固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃。动态分配 的时候,存储数组的空间是在执行过程中确认的,一旦数据空间占满了,就另外开辟一块更大的空间,用以替换原来的空间,从而达到扩充数组的目的,而不需要为线性表一次性划分所有的空间。 优点主要特点是随机读取,可以通过 O ( 1 ) O(1) O ( 1 ) 内通过首地址和元素序号找到指定的元素。 存储密度高,每个节点只存储数据元素 缺点逻辑上相邻的元素物理上也相邻,所以插入元素和删除元素需要移动大量的元素 假定顺序表的元素类型为 ElemType
,则线性表的顺序存储类型描述为:
基本操作的实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #include <stdio.h> #include <stdbool.h> #define Maxsize 9999 typedef int ElemType;typedef struct { ElemType data[Maxsize]; int length; } Sqlist; void InitList (Sqlist *L) { L->length = 0 ; } bool ListInsert (Sqlist *L, int index, ElemType target) { if (index < 1 || index > L->length + 1 ) return false ; if (L->length >= Maxsize) return false ; for (int i = L->length; i >= index; i--) { L->data[i] = L->data[i - 1 ]; } L->data[index - 1 ] = target; L->length++; return true ; } bool ListDelete (Sqlist *L, int index, ElemType *value) { if (index < 1 || index > L->length) return false ; *value = L->data[index - 1 ]; for (int i = index; i < L->length; i++) { L->data[i - 1 ] = L->data[i]; } L->length--; return true ; } int LocateElem (Sqlist *L,ElemType e) { for (int i=0 ;i<L->length;i++) { if ( L->data[i] == e ) return i+1 ; } return 0 ; } int main (void ) { Sqlist *L; InitList (L); for (int i = 0 ; i < L->length; i++) { printf ("data[%d]=%d\n" , i, L->data[i]); } return 0 ; }
插入情况分析最好情况:直接表尾插入,不需要移动元素,时间复杂度为 O ( 1 ) O(1) O ( 1 ) 最坏情况 :需要在表头插入,需要移动 n n n 个元素,时间复杂度为 O ( n ) O(n) O ( n ) 平均情况 :假设 P i = 1 ( n + 1 ) P_{i} = \frac {1} {(n+1)} P i = ( n + 1 ) 1 是在第 i i i 个元素上插入一个节点的概率,则在长度为 n n n 的线性表中插入一个结点时所需要移动的结点的平均次数(数学期望 E x Ex E x )为∑ i = 1 n + 1 p i ( n − i + 1 ) = ∑ i = 1 n + 1 ( n − i + 1 ) n + 1 = n + 1 n + 1 ∗ 1 n + 1 ∗ ∑ i = 1 n + 1 − i = n 2 \sum_{i=1}^{n+1} p_{i}(n-i+1) = \sum_{i=1}^{n+1} \frac {(n-i+1)} {n+1} = \frac {n+1} {n+1} *\frac {1} {n+1}*\sum_{i=1}^{n+1} -i = \frac {n} {2} i = 1 ∑ n + 1 p i ( n − i + 1 ) = i = 1 ∑ n + 1 n + 1 ( n − i + 1 ) = n + 1 n + 1 ∗ n + 1 1 ∗ i = 1 ∑ n + 1 − i = 2 n
因此,顺序表插入算法的平均时间复杂度是 $O({n})$
删除操作情况分析描述:删除顺序表 L L L 中第 i i i 个位置的元素,用引用变量 e 返回。若 i i i 的输入不合法,则返回 false
;否则,将被删的元素赋给 e e e ,并将第 i + 1 i+1 i + 1 个元素移动及其后的元素依次向前移动一个位置,返回 true
最好情况:直接删除表尾元素,不需要移动元素,时间复杂度为 O ( 1 ) O(1) O ( 1 ) 最坏情况 :需要删除表头元素,需要移动 n n n 个元素,时间复杂度为 O ( n ) O(n) O ( n ) 平均情况 :假设 P i = 1 ( n ) P_{i} = \frac {1} {(n)} P i = ( n ) 1 是在第 i i i 个元素上删除一个节点的概率,则在长度为 n n n 的线性表中插入一个结点时所需要移动的结点的平均次数(数学期望 E x Ex E x )为∑ i = 1 n p i ( n − i ) = ∑ i = 1 n ( n − i ) n = n n ∗ 1 n ∗ ∑ i = 1 n − i = n − 1 2 \sum_{i=1}^{n} p_{i}(n-i) = \sum_{i=1}^{n} \frac {(n-i)} {n} = \frac {n} {n} *\frac {1} {n}*\sum_{i=1}^{n} -i = \frac {n-1} {2} i = 1 ∑ n p i ( n − i ) = i = 1 ∑ n n ( n − i ) = n n ∗ n 1 ∗ i = 1 ∑ n − i = 2 n − 1
链表
线性表的链式存储又称单链表 ,它是指通过一组任意的存储单元来存储线性表中的数据元素。
代码实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdbool.h> #include <malloc.h> typedef int ElemType;typedef union { ElemType *L; ElemType data; } Data; struct LNode { Data data; struct LNode *next ; }; typedef struct LNode *LinkList ;typedef struct LNode Node ;Node *GetElm (LinkList L, int i) { Node *p = L->next; if (i == 0 ) return L; if (i < 1 ) return NULL ; for (int j = 0 ; p && j < i; p = p->next, ++j); return p; }; bool ListInsert (LinkList L, int i, ElemType e) { if (i < 1 ) return false ; Node *p = L; for (int j = 0 ; p && j < i - 1 ; p = p->next, ++j); if (!p) return false ; Node *s = (LinkList) malloc (sizeof (Node)); s->data.data = e; s->next = p->next; p->next = s; return true ; }
优点利用单链表可以解决顺序表需要大量连续存储单元的缺点 缺点附加单链表附加指针域,也存在浪费存储空间的缺点。 非随机存取的存储结构,即不能直接找到表中某个特定的结点。–单向检索 关于 头节点 和 头指针 的区分:
不管带不带头节点,头指针都始终指向链表的第一个结点,而头节点是带头结点的链表中的第一个结点,结点内通常不存信息。
引入头节点之后,可以带来两个优点
由于第一个数据节点的位置被存放在头节点的指针域中,因此在链表的第一个位置上的操作的其他位置上的操作一致,无须特殊处理。 无论链表是否为空,其头指针都指向头节点的非空指针,因此空表和非空表的处理也就得到了统一。