目录
一,线性表
- 具有相同特性的数据元素的有限序列,是线性数据结构中最简单、最基本、最常用的;
- 在逻辑上是线性结构(是连续的一条直线),但在物理结构上并不一定是连续的;
- 线性表在物理上存储时,通常以数组和链式结构的形式存储;
- 常见的线性表:顺序表、链表,及栈、队列、字符串等;
二,顺序表
- 用一段物理地址连续的存储单元,依次存储数据元素的线性结构;
- 一般情况下采用数组存储,在数组上完成数据的增删查改;
顺序表分类
- 静态顺序表:使用定长数组存储元素;
- 动态顺序表:使用动态开辟的数组存储;
//静态顺序表,顺序表的静态存储
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
SLDataType data[N]; //定长数组
size_t size; //有效数据个数
}SeqList;
//动态顺序表,顺序表的动态存储
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* data; //指向动态开辟的数组
size_t size; //有效数据个数
size_t capicity; //容量空间(涉及扩容)
}SeqList;
顺序表缺陷
- 增容需申请新空间,还可能需拷贝数据和释放旧空间,会有不小的消耗;
- 增容一般为2倍增容,会有一定空间浪费;
- 插入、删除,时间复杂度为O(N),效率较低;
接口实现
- 由于静态顺序表,大小固定,现实中一般使用动态顺序表,大小可调整;
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 顺序表销毁
void SeqListDestroy(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表打印
void SeqListPrint(SeqList* psl);
注:完整接口实现代码路径;
试题
三,链表
- 在物理存储结构上非连续、非顺序的;
- 数据元素的逻辑顺序是通过链表中的指针链接次序实现的 ;
链表分类
- 单向或双向链表
- 带头或不带头链表(哨兵位)
- 循环或不循环链表
单链表(单向无头不循环)
- 结构简单,一般不会单独存数据,笔试、面试较多;
//单链表(无头单向不循环)
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SListNode;
接口实现
//基本增删查改接口
//动态申请空间,生成一个节点
SListNode* BuySListNode(SLDataType x);
//传二级指针,是因为要在函数外修改phead
//单链表尾插
void SListPushBack(SListNode** pphead, SLDataType x);
//单链表头插
void SListPushFront(SListNode** pphead, SLDataType x);
//单链表尾删
void SListPopBack(SListNode** pphead);
//单链表头删
void SListPopFront(SListNode** pphead);
//单链表查找,先查找根据返回位置进行修改(无需在增加修改接口)
SListNode* SListFind(SListNode* phead, SLDataType x);
//单链表指定位置前插入,先查找在根据返回位置插入(插入时还需遍历链表效率低)
//单链表不适合在指定位置前面插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLDataType x);
//单链表指定位置后插入,先查找在根据返回位置插入
void SListInsertAfter(SListNode* pos, SLDataType x);
//单链表指定位置删除,先查找在根据返回位置删除(删除时还需遍历链表效率低)
//单链表不适合在指定位置删除
void SListErase(SListNode** pphead, SListNode* pos);
//单链表指定位置后删除
void SListEraseAfter(SListNode* pos);
//单链表节点释放
void SListDestroy(SListNode** pphead);
//单链表打印
void SListPrint(SListNode* phead);
//单链表节点个数
int SListSize(SListNode* phead);
//是否为空链表
bool SListEmpty(SListNode* phead);
注:完整接口实现代码路径;
试题
- 1,移除链表指定元素;(创建新头并尾插,使用哨兵节点更方便)
- 2,反转一个单链表;(创建新头并头插)
- 3,返回单链表的中间节点;(快慢指针,2倍)
- 4,返回链表中倒数第K个节点;(快慢指针,快指针先走K步)
- 5,合并两个有序链表;(遍历两链表,将较小的尾插到新链表,使用哨兵节点更方便)
- 6,链表分割(小于给定值的放链表前面,且相对位置不变);(创建两链表,小于指定值的链表和大于指定值的链表,在链接,末尾节点需置空,使用哨兵节点更方便)
- 7,链表的回文结构;(先找到中间节点,在逆置中间以后的节点,在比较)
- 8,相交单链表,返回相交起始节点;(长链表先走相差个数,在一一比较地址,相同即相交)
- 9,给定单链表,返回入环起始节点,无环返回NULL;(1,先判断是否有环并返回相遇节点,在从头和从相遇节点遍历,相遇即入环起始节点;2,断开相遇节点,可转化为相交求交点;)
- 10,复制带随机指针的链表;(先在原链表后复制每个节点,在通过相对位置赋值random,最后尾插到新链表并恢复原链表)
判断链表是否带环
分析:假设fast比slow快一倍,则L=M
或
推导出:
或
结论:
头节点到入环起始节点之间的节点数等于相遇节点到入环起始节点之间的节点数!
即,一指针从头走,一指针从相遇节点走,每次均走一步,最终会在入环节点相遇;
带头双向循环链表
- 带头双向循环链表,结构最复杂,一般用于单独存储数据;
- 结构优势明显,实际使用的数据结构;
//双向链表
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
接口实现
//基本增删查改接口
//初始化链表,会创建头节点(哨兵)
void ListInit(LTNode** pphead);
//释放链表节点
void ListDestroy(LTNode* phead);
//链表尾插
void ListPushBack(LTNode* phead, LTDataType x);
//链表尾删
void ListPopBack(LTNode* phead);
//链表头插
void ListPushFront(LTNode* phead, LTDataType x);
//链表头删
void ListPopFront(LTNode* phead);
//查找节点
ListNode* ListFind(LTNode* phead, LTDataType x);
//指定位置前插入
void ListInsert(LTNode* pos, LTDataType x);
//指定位置删除
void ListErase(LTNode* pos);
//打印链表
void ListPrint(LTNode* phead);
//求链表的节点数
size_t ListSize(LTNode* phead);
//判断是否为空链表
bool ListEmpty(LTNode* phead);
注:完整接口实现代码路径;
四,备注
- 存储体系结构及局部原理;
- cpu访问内存时,会读取一段数据到缓存中;