目录
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
(1) 静态顺序表:使用定长数组存储元素。
(2) 动态顺序表:使用动态开辟的数组存储。
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
typedef int SLDataType; //动态顺序表 typedef struct SeqList { SLDataType* a;//指向动态数组的指针 int size; //数据个数 int capacity; //容量-空间大小 }SL; //顺序表初始化 void SLInit(SL* ps); // 顺序表尾插 void SLPushBack(SL* ps, SLDataType x); // 顺序表尾删 void SLPopBack(SL* ps); // 顺序表头插 void SLPushFront(SL* ps, SLDataType x); // 顺序表头删 void SLPopFront(SL* ps); // 检查空间,如果满了,进行增容 void SLCheckCapacity(SL* ps); // 顺序表查找 int SLFind(SL* ps, SLDataType x); //顺序表修改 void SLModify(SL* ps, int pos, SLDataType x); // 顺序表在pos位置插入x void SLInsert(SL* ps, int pos, SLDataType x); // 顺序表删除pos位置的值 void SLErase(SL* ps, int pos); // 顺序表销毁 void SLDestory(SL* ps); // 顺序表打印 void SLPrint(SL* ps);接口的实现:
void SLPrint(SL* ps) { assert(ps); for (int i = 0; i < ps->size; i++) { printf("%d ", ps->a[i]); } printf("\n"); } void SLInit(SL* ps) { assert(ps); ps->a = NULL; ps->size = ps->capacity = 0; } void SLCheckCapacity(SL* ps) { assert(ps); if (ps->capacity == ps->size) { int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType)); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } ps->a = tmp; ps->capacity = newCapacity; } } int SLFind(SL* ps, SLDataType x) { assert(ps); for (int i = 0; i < ps->size; i++) { if (ps->a[i] = x) return i; } return -1; } void SLModify(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos <= 0 && pos > ps->size); ps->a[pos] = x; } void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos < ps->size); SLCheckCapacity(ps); int end = 0; while (end >= pos) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[pos] = x; ps->size++; } void SLErase(SL* ps, int pos) { assert(pos); assert(pos >= 0 && pos < ps->size); int begin = pos; while (begin < ps->size - 1) { ps->a[begin] = ps->a[begin + 1]; begin++; } ps->size--; } void SLPushBack(SL* ps, SLDataType x) { assert(ps); SLCheckCapacity(ps); ps->a[ps->size] = x; ps->size++; } void SLPopBack(SL* ps) { assert(ps); assert(ps->size > 0); ps->size--; } void SLPushFront(SL* ps, SLDataType x) { assert(ps); SLCheckCapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[0] = x; ps->size++; } void SLPopFront(SL* ps) { assert(ps); assert(ps->size > 0); int begin = 0; while(begin < ps->size - 1) { ps->a[begin + 1] = ps->a[begin]; begin++; } ps->size--; } void SLDestory(SL* ps) { assert(ps); if (ps->a) { free(ps->a); ps->a = NULL; ps->capacity = ps->size = 0; } }
2.3 顺序表的问题及思考
问题:
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看。
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
3.3.1 无头+单向+非循环链表增删查改实现
typedef int SLTDataType; typedef struct SListNode { struct SListNode* next; SLTDataType data; }SLTNode; //开辟空间 SLTNode* BuySListNode(SLTDataType x); //打印 void SListPrint(SLTNode* phead); //尾插 void SListPushBack(SLTNode** pphead, SLTDataType x); //头插 void SListPushFront(SLTNode** pphead, SLTDataType x); //尾删 void SListPopBack(SLTNode** pphead); //头删 void SListPopFront(SLTNode** pphead); //查找 SLTNode* SListFind(SLTNode* phead, SLTDataType x); // 在pos位置之前插入 void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x); // 删除pos位置的值 void SListErase(SLTNode** pphead, SLTNode* pos); // 在pos位置后面插入 void SListInsertAfter(SLTNode* pos, SLTDataType x); // 删除pos位置后面的值 void SListEraseAfter(SLTNode* pos);接口的实现:
SLTNode* BuySListNode(SLTDataType x) { SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode)); assert(newNode); newNode->data = x; newNode->next = NULL; return newNode; } void SListPrint(SLTNode* phead) { assert(phead); SLTNode* cur = phead; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); } void SListPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newNode = BuySListNode(x); if (*pphead == NULL) { *pphead = newNode; } else { // 找尾节点 SLTNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newNode; } } void SListPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newNode = BuySListNode(x); newNode->next = *pphead; *pphead = newNode; } void SListPopBack(SLTNode** pphead) { assert(pphead); assert(*pphead); // 1、只有一个节点 // 2、多个节点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SLTNode* tail = *pphead; while (tail->next->next != NULL) { tail = tail->next; } free(tail->next); tail->next = NULL; } } void SListPopFront(SLTNode** pphead) { assert(pphead); assert(*pphead != NULL); //if (*pphead == NULL) // return; SLTNode* next = (*pphead)->next; free(*pphead); *pphead = next; } SLTNode* SListFind(SLTNode* phead, SLTDataType x) { SLTNode* cur = phead; while (cur) { if (cur->data == x) return cur; cur = cur->next; } return NULL; } void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pos); assert(pphead); // 头插 if (pos == *pphead) { SListPushFront(pphead, x); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } SLTNode* newnode = BuySListNode(x); prev->next = newnode; newnode->next = pos; } } void SListErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(pos); if (*pphead == pos) { SListPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); pos = NULL; } } void SListInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); // 不在乎链接顺序 SLTNode* newnode = BuySListNode(x); SLTNode* next = pos->next; pos->next = newnode; newnode->next = next; } void SListEraseAfter(SLTNode* pos) { assert(pos); if (pos->next == NULL) return; SLTNode* del = pos->next; pos->next = del->next; free(del); del = NULL; }
3.3.2 带头+双向+循环链表增删查改实现
typedef int LTDataType; typedef struct ListNode { struct ListNode* next; struct ListNode* prev; LTDataType data; }LTNode; //初始化 LTNode* ListInit(); //打印 void ListPrint(LTNode* phead); //尾插 void ListPushBack(LTNode* phead, LTDataType x); //头插 void ListPushFront(LTNode* phead, LTDataType x); //尾删 void ListPopBack(LTNode* phead); //头删 void ListPopFront(LTNode* phead); //判断是否为空 bool ListEmpty(LTNode* phead); // 在pos位置之前插入x void ListInsert(LTNode* pos, LTDataType x); // 删除pos位置的节点 void ListErase(LTNode* pos);接口的实现:
LTNode* BuyListNode(LTDataType x) { LTNode* newNode = (LTNode*)malloc(sizeof(LTNode)); if (newNode == NULL) { perror("malloc fail"); exit(-1); } newNode->data = x; newNode->next = NULL; newNode->prev = NULL; return newNode; } LTNode* ListInit() { LTNode* phead = BuyListNode(-1); phead->next = phead; phead->prev = phead; return phead; } void ListPrint(LTNode* phead) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { printf("%d ", cur->data); cur = cur->next; } printf("\n"); } void ListPushBack(LTNode* phead, LTDataType x) { assert(phead); LTNode* newNode = BuyListNode(x); LTNode* tail = phead->prev; tail->next = newNode; newNode->prev = tail; newNode->next = phead; phead->prev = newNode; } void ListPushFront(LTNode* phead, LTDataType x) { assert(phead); LTNode* newNode = BuyListNode(x); LTNode* next = phead->next; newNode->next = next; next->prev = newNode; newNode->prev = phead; phead->next = newNode; } void ListPopBack(LTNode* phead) { assert(phead); assert(phead->next != phead); LTNode* tail = phead->prev; LTNode* tailPrev = tail->prev; free(tail); tailPrev->next = phead; phead->prev = tailPrev; } void ListPopFront(LTNode* phead) { assert(phead); assert(phead->next != phead); LTNode* cur = phead->next; LTNode* next = phead->next->next; phead->next = next; next->prev = phead; free(cur); } bool ListEmpty(LTNode* phead) { assert(phead); return phead->next == phead; } void ListInsert(LTNode* pos, LTDataType x) { assert(pos); LTNode* prev = pos->prev; LTNode* newnode = BuyListNode(x); prev->next = newnode; newnode->prev = prev; newnode->next = pos; pos->prev = newnode; } void ListErase(LTNode* pos) { assert(pos); LTNode* prev = pos->prev; LTNode* next = pos->next; prev->next = next; next->prev = prev; free(pos); }
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定 连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
任意位置插入或者删除 元素 | 可能需要搬移元素,效率低 O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要 扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
备注:缓存利用率参考存储体系结构 以及 局部原理性。