Lesson3–顺序表和链表
1. 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列.线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表,链表,栈,队列,字符串…
线性表在逻辑上是线性结构,但在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储.
2. 顺序表
2.1 概念及结构
顺序表时用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储.在数组上完成数据的增删查改.
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素
//顺序表的静态存储 #define N 7 typedef int SLDataType; typedef struct SeqList { SLDataType array[N];//定长数组 size_t size;//有效数据的个数 }SeqList;
- 动态顺序表:使用动态开辟的数组存储
//顺序表的动态存储 typedef struct SeqList { SLDataType* array;//指向动态开辟的数组 size_t size;//有效数据个数 size_t capacity;//容量空间的大小 }SeqList;
2.2 接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致空间开多了浪费,开少了不够用.所以现实中基本都是使用动态顺序表,根据需要动态地分配空间大小.
//顺序表的动态存储
typedef int SLDataType;
//sequence list
typedef struct SeqList
{
int* array; //指向动态开辟的数组
int size; //有效数据个数
int capacity; //空间容量大小
}SL;
//顺序表初始化
void SLInit(SL* psl);
//顺序表销毁时
void SLDestroy(SL* psl);
//顺序表打印
void SLPrint(SL* psl);
//检查空间,如果满了,则扩容
void SLCheckCapacity(SL* psl);
//顺序表尾插
void SLPushBack(SL* psl, SLDataType x);
//顺序表头插
void SLPushFront(SL* psl, SLDataType x);
//顺序表尾删
void SLPopBack(SL* psl);
//顺序表头删
void SLPopFront(SL* psl);
//顺序表pos位置插入
void SLInsert(SL* psl, int pos, SLDataType x);
//顺序表pos位置删除
void SLErase(SL* psl, int pos);
//顺序表查找
int SLFind(SL* psl, SLDataType x);
3. 链表
3.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的.
注意
- 链式结构在逻辑上是连续的,但在物理上不一定连续.
- 现实中的节点一般都是从堆上申请出来的.
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续.
3.2 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
-
单向或双向
-
带头或不带头
-
循环或非循环
实际中最常用的两种结构:
- 无头单向非循环链表:结构简单,一般不会单独用来存数据.实际中更多是作为其它数据结构的子结构.
- 带头双向循环链表:结构最复杂,一般用在单独存储数据.实际中使用的链表数据结构,都是带头双向循环链表.
3.3 链表的实现
//无头单向非循环链表
typedef int SLNDataType;
typedef struct SListNode
{
SLNDataType val;
struct SListNode* next;
}SLNode;
//单链表打印
void SLTPrint(SLNode* phead);
//单链表尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);
//单链表头插
void SLTPushFront(SLNode** pphead, SLNDataType x);
//单链表尾删
void SLTPopBack(SLNode** pphead);
//单链表头删
void SLTPopFront(SLNode** pphead);
//单链表查找
SLNode* SLTFind(SLNode* phead, SLNDataType x);
//在pos前插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
//在pos后插入
void SLTInsertAfter(SLNode* pos, SLNDataType x);
//删除pos位置后的值
void SLTErase(SLNode** pphead, SLNode* pos);
//单链表销毁
void SLTDestroy(SLNode** pphead);
3.4 双向链表的实现
//带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
//创建返回链表的头结点.
ListNode* ListCreate();
//双向链表销毁
void ListDestory(ListNode* plist);
//双向链表打印
void ListPrint(ListNode* plist);
//双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
//双向链表尾删
void ListPopBack(ListNode* plist);
//双向链表头插
void ListPushFront(ListNode* plist, LTDataType x);
//双向链表头删
void ListPopFront(ListNode* plist);
//双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
//双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
//双向链表删除pos位置的结点
void ListErase(ListNode* pos);
4. 顺序表和链表的区别
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间 | 物理上一定连续 | 逻辑上连续物理上不一定连续 |
随机访问 | 支持: O ( 1 ) O(1) O(1) | 不支持: O ( N ) O(N) O(N) |
任意位置插入或删除元素 | 可能需要搬移元素,效率低$O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置频繁插入和删除 |
缓存利用率 | 高 | 低 |