线性表
线性表n个具有相同特性的数据元素的有限序列,它是一种在实际使用中广泛使用的数据结构,常见的线性表有顺序表、链表、栈、队列等。线性表在逻辑上是线性结构,但是在物理上不一定连续(比如链表)。
顺序表
顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般用数组存储,在数组上完成数据的增删查改。
顺序表一般分为:
- 静态顺序表(不常用):使用定长数组存储。
1 2 3 4 5 6 7
| #define N 100 typedef int Type; typedef struct SeqList { Type array[N]; size_t size; }SeqList;
|
- 动态顺序表:使用动态开辟的数组存储
1 2 3 4 5 6 7
| typedef int Type typedef struct SeqList { Type* array; size_t size; size_t capicity; }SeqList;
|
一般来说,更常使用的是动态顺序表,因为它更灵活一些。
顺序表的接口实现
- 顺序表的增,可分为头插,尾插,任意位置插入。它们的时间复杂度分别为头插:O(n)、尾插:O(1)、任意位置插入:O(n)。代码如下:
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
| void SeqListPushFront(SeqList* ps, SLDateType x) { if (ps->size >= ps->capacity) { Realoc(ps); } size_t end = ps->size; while (end > 0) { ps->a[end] = ps->a[end - 1]; end--; } ps->a[0] = x; ps->size++; }
void SeqListPushBack(SeqList* ps, SLDateType x) { if (ps->size >= ps->capacity) { Realoc(ps); } ps->a[ps->size] = x; ps->size++; }
void SeqListInsert(SeqList* ps, size_t pos, SLDateType x) { if (ps->size >= ps->capacity) { Realoc(ps); } size_t end = ps->size; while (end > (pos - 1)) { ps->a[end] = ps->a[end - 1]; end--; } ps->a[pos - 1] = x; ps->size++; }
|
- 顺序表的删也可以分为头删、尾删以及任意位置删除,它们的时间复杂度分别为,头删:O(n)、尾删:O(1)、任意位置删除:O(n)。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void SeqListPopFront(SeqList* ps) { for (size_t i = 0; i < ps->size; i++) { ps->a[i] = ps->a[i + 1]; } ps->size--; }
void SeqListPopBack(SeqList* ps) { ps->size--; }
void SeqListErase(SeqList* ps, size_t pos) { size_t erase = pos - 1; while (erase < ps->size) { ps->a[erase] = ps->a[erase + 1]; erase++; } ps->size--; }
|
- 顺序表的查找需要遍历整个表,故时间复杂度为O(n),代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| int SeqListFind(SeqList* ps, SLDateType x) { size_t find = 0; while (find < ps->size) { if (ps->a[find] == x) { return find + 1; } find++; } return -1; }
|
- 由于顺序表可以支持随机访问的特点,所以他的数据的修改比较简单,在这里不多赘述。
链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的结构非常多样,具有的特征有:
- 单向、双向
- 带头、不带头
- 循环、非循环
以上的情况组合起来就有23 = 8 种链表结构。实际中最常用的是无头单项非循环链表以及带头双向循环链表
不带头单向非循环链表
- 链表的实现
1 2 3 4 5 6 7 8
| typedef int SLTDateType;
typedef struct SListNode { SLTDateType data; struct SListNode* next; }SListNode;
|
- 创建一个节点以及打印数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| SListNode* BuySListNode(SLTDateType x) { SListNode* new = (SListNode*)malloc(sizeof(SListNode)); new->data = x; new->next = NULL; return new; }
void SListPrint(SListNode* plist) { SListNode* cur = plist; while (cur) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); }
|
- 插入节点,头插:O(1)、尾插O(n)、任意位置之后插入O(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 41 42
| void SListPushFront(SListNode** pplist, SLTDateType x) { SListNode* new = BuySListNode(x); if (*pplist == NULL) { *pplist = new; return; } new->next = *pplist; *pplist = new; }
void SListPushBack(SListNode** pplist, SLTDateType x) { SListNode* new = BuySListNode(x); if (*pplist == NULL) { *pplist = new; return; } SListNode* cur = *pplist; while (cur->next) { cur = cur->next; } cur->next = new; }
void SListInsertAfter(SListNode* pos, SLTDateType x) { if (pos == NULL) { printf("不存在!\n"); return; } SListNode* new = BuySListNode(x); SListNode* next = pos->next; pos->next = new; new->next = next; }
|
- 删除节点,头删O(1)、尾删O(n)、任意位置删除O(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 41 42 43 44 45 46 47 48 49
| void SListPopFront(SListNode** pplist) { SListNode* first = *pplist; if (first == NULL || first->next == NULL) { free(first); *pplist = NULL; return; } SListNode* after = first->next; free(first); *pplist = after;
}
void SListPopBack(SListNode** pplist) { SListNode* back = *pplist; SListNode* before = NULL; if (back == NULL || back->next == NULL) { free(back); *pplist = NULL; return; }
while (back->next) { before = back; back = back->next; } free(back); back = NULL; before->next = NULL; }
void SListEraseAfter(SListNode* pos) { if (pos == NULL) { printf("不存在!\n"); return; } SListNode* del = pos->next; SListNode* del_next = del->next; free(del); pos->next = del_next; }
|
- 查找
1 2 3 4 5 6 7 8 9 10 11 12
| SListNode* SListFind(SListNode* plist, SLTDateType x) { SListNode* find = plist; while (find) { if (find->data == x) return find; find = find->next; } return NULL; }
|
- 链表的修改较为简单,只需要找到该位置并修改数值即可,在此不多赘述。
带头双向循环列表
- 链表的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| typedef int LTDataType; typedef struct ListNode { LTDataType _data; struct ListNode* _next; struct ListNode* _prev; }ListNode;
void Init(ListNode* pHead) { pHead->_data = 0; pHead->_next = pHead; pHead->_prev = pHead; }
|
- 创建一个节点以及打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ListNode* ListCreate(LTDataType x) { ListNode* Newnode = (ListNode*)malloc(sizeof(ListNode)); Newnode->_data = x; Newnode->_prev = NULL; Newnode->_next = NULL;
return Newnode; } void ListPrint(ListNode* pHead) { ListNode* cur = pHead->_next; while (cur != pHead) { printf("%d ", cur->_data); cur = cur->_next; } printf("\n"); }
|
- 插入数据,头插O(1)、尾插O(1)、任意位置之前插入O(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
| void ListPushFront(ListNode* pHead, LTDataType x) { ListNode* Newnode = ListCreate(x); ListNode* Front = pHead->_next;
Newnode->_next = Front; Front->_prev = Newnode;
pHead->_next = Newnode; Newnode->_prev = pHead; } void ListPushBack(ListNode* pHead, LTDataType x) { ListNode* Newnode = ListCreate(x); ListNode* last = pHead->_prev;
last->_next = Newnode; Newnode->_prev = last;
Newnode->_next = pHead; pHead->_prev = Newnode;
} void ListInsert(ListNode* pos, LTDataType x) { ListNode* Newnode = ListCreate(x); ListNode* before = pos->_prev;
Newnode->_prev = before; Newnode->_next = pos;
before->_next = Newnode; pos->_prev = Newnode; }
|
- 删除数据,头删O(1)、尾删O(1)、任意位置删除O(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
| void ListPopFront(ListNode* pHead) { ListNode* Front = pHead->_next; pHead->_next = Front->_next; Front->_next->_prev = pHead;
free(Front); } void ListPopBack(ListNode* pHead) { ListNode* last = pHead->_prev; pHead->_prev = last->_prev; last->_prev->_next = pHead;
free(last); } void ListErase(ListNode* pos) { ListNode* before = pos->_prev; ListNode* after = pos->_next;
before->_next = after; after->_prev = before;
free(pos); }
|
- 查找
1 2 3 4 5 6 7 8 9 10 11
| ListNode* ListFind(ListNode* pHead, LTDataType x) { ListNode* cur = pHead->_next; while (cur != pHead) { if (cur->_data == x) return cur; cur = cur->_next; } return NULL; }
|
- 链表的修改较为简单,只需要找到该位置并修改数值即可,在此不多赘述。
顺序表和链表各自的特点
顺序表:
- 优点:支持随机访问,尾插尾删时间复杂度O(1),连续的结构,实现简单。
- 缺点:其他位置的插入删除时间复杂度都为O(n),并且在插入时有增容代价。
- 适用场景:适合频繁访问的场景
链表(指双向链表):
- 优点:任意位置插入删除时间复杂度都是O(1)。
- 缺点:不支持随机访问,实现比较复杂(但用起来简单),非连续结构。
- 适用场景:频繁插入删除的场景。