目录
一 顺序表
1.顺序表本质就是数组,动态增长(顺序表的动态存储),并且要求里面的数据是从左向右连续存储的。顺序表逻辑结构与物理结构一致。
2.顺序表缺陷:(1)动态增容有性能消耗。(2)头部插入需要挪动数据。
3.顺序表优点:支持随机访问。
4.顺序表动态存储的实现。
①顺序表的结构
//顺序表(sequence list)
typedef struct SeqList
{
SeqDateType* a; //指向动态开辟的数组
int size; //有效数据的个数
int capacity; //容量
}SeqList,SEQ; //将srtuct SeqList 替换为 SeqList或者SEQ
②顺序表的初始化和销毁
//顺序表初始化
void SeqListInit(SeqList* pq)
{
assert(pq);
pq->a =NULL;
pq->size = pq->capacity = 0;
}
//顺序表的销毁
void SeqListDestory(SeqList* pq)
{
assert(pq);
free(pq->a);
pq->a = NULL;
pq->capacity = pq->size = 0;
}
③顺序表增容
//顺序表增容
void CheckCapacity(SeqList* pq)
{
//满了需要增容
if (pq->size == pq->capacity)
{
int newcapacity = pq->capacity == 0 ? 4 : pq->capacity * 2;
SeqDateType * newA = (SeqDateType *)realloc(pq->a,sizeof(SeqDateType)* newcapacity);
if (newA == NULL)
{
printf("realloc fail\n");
exit(-1);
}
pq->a = newA;
pq->capacity = newcapacity;
}
}
④顺序表数据插入
//头插
void SeqListPushFront(SeqList* pq, SeqDateType x)
{
assert(pq);
CheckCapacity(pq);
int end = pq->size-1;
while (end>=0)
{
pq->a[end + 1] = pq->a[end];
end--;
}
pq->a[0] = x;
pq->size++;
}
//尾插
void SeqListPushBack(SeqList* pq, SeqDateType x)
{
assert(pq);
CheckCapacity(pq);
pq->a[pq->size] = x;
pq->size++;
}
⑤顺序表的数据删除
//头删
void SeqListPopFront(SeqList* pq)
{
assert(pq);
assert(pq->size > 0);
for(int i=0;i<pq->size;i++)
{
pq->a[i] = pq->a[i+1];
}
pq->size--;
}
//尾删
void SeqListPopBack(SeqList* pq)
{
assert(pq);
assert(pq->size > 0);
--pq->size;
}
⑥顺序表中数据的查找
//查找数据
int SeqListFind(SeqList* pq, SeqDateType x)
{
assert(pq);
assert(pq->size > 0);
for (int i = 0; i < pq->size; i++)
{
if (x == pq->a[i])
{
return i;
}
}
return -1;
}
⑦指定位置插入数据(可复用,优化头插与尾插)
//指定位置插入数据
void SeqListInsert(SeqList* pq, int pos, SeqDateType x)
{
assert(pq);
CheckCapacity(pq);
assert(pq->size>=pos && pos>=0);
int end = pq->size - 1;
while (end >= pos)
{
pq->a[end + 1] = pq->a[end];
end--;
}
pq->a[pos] = x;
pq->size++;
}
⑧删除指定位置的数据(可复用,优化头删与尾删)
//指定位置删除
void SeqListErase(SeqList* pq, int pos)
{
assert(pq);
assert(pq->size > 0 && pos >= 0);
for (int i = pos; i < pq->size-1; i++)
{
pq->a[i] = pq->a[i+1];
}
pq->size--;
}
⑨顺序表修改pos位置的值
// 顺序表修改pos位置的值
void SeqListModify(SeqList* pq, int pos, SeqDateType x)
{
assert(pq);
assert(pos >= 0 && pos < pq->size);
pq->a[pos] = x;
}
二 无头、单向、非循环链表链表
1.概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。通常作为哈希桶、图的邻接表的子结构使用。
2.链表的优点:(1)空间按需索取。(2)头插不需要挪动数据。
3.缺点:不支持随机访问。
4.无头、单向、非循环链表的实现。
①结构
typedef int SLTDateType;
//单链表的结构
typedef struct SListNode
{
SLTDateType date; //存放单链表的数据
struct SListNode* next;//存放下一个节点的地址
}SListNode;
②动态申请一个节点
//创建新的节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
if (node!=NULL)
{
node->date = x;
node->next = NULL;
return node;
}
}
③单链表打印
//打印单链表
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->date);
cur = cur->next;
}
printf("NULL\n");
}
④单链表头插尾插
//尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
//如果链表为空
if (*pplist == NULL)
{
*pplist = newnode;
}
else //不为空
{
SListNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* newnode= BuySListNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
⑤单链表头删尾删
//尾删
void SListPopBack(SListNode** pplist)
{
//链表为空
if (*pplist == NULL)
{
return;
}
else if((*pplist)->next==NULL) //链表有一个节点
{
free(*pplist);
*pplist = NULL;
}
else //有多个节点
{
SListNode* pre = NULL;
SListNode* tail = *pplist;
while (tail->next != NULL)
{
pre = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
pre->next = NULL;
}
}
//头删
void SListPopFront(SListNode** pplist)
{
//链表为空
if (*pplist == NULL)
{
return;
}
else //不为空
{
SListNode* next =(*pplist)->next;
free(*pplist);
*pplist = next;
}
}
⑥单链表数据查找(兼具修改的作用)
//单链表的查找
SListNode* SListPopFind(SListNode* plist, SLTDateType x)
{
SListNode* cur = plist;
while (NULL != cur)
{
if (x == cur->date)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//SListNode* pos=SListPopFind(plist, 6);
//if (NULL != pos)
//{
// printf("找到了\n");
// pos->date = 100;
//}
//else
//{
// printf("没找到\n");
//}
//SListPrint(plist);
⑦单链表在指定位置之后插入数据
//指定位置之后插入数据
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
⑧单链表删除在指定位置之后的数据
//删除指定位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (NULL == pos->next)
{
return;
}
else
{
SListNode* next = pos->next;
pos->next = next->next;
free(next);
}
}
三 带头双向循环链表的实现
1.双向链表结构与链表初始化
//结构
typedef int LTDateType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDateType date;
}ListNode;
//创建新的节点
ListNode* BuyListNode(LTDateType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
assert(node);
node->next = NULL;
node->prev = NULL;
node->date = x;
return node;
}
//链表初始化(创建头节点)
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
2.打印双向链表
//打印双向链表
void ListNodePrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d->", cur->date);
cur = cur->next;
}
printf("\n");
}
3.链表的数据插入
//在指定位置之前插入数据
void ListInisert(ListNode* pos, LTDateType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
pos->prev = newnode;
newnode->prev = prev;
prev->next = newnode;
newnode->next = pos;
}
//尾插
void ListPushBack(ListNode* phead, LTDateType x)
{
//assert(phead);
//ListNode* tail = phead->prev;
//ListNode* newnode = BuyListNode(x);
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
ListInisert(phead, x);//复用替换
}
//头插
void ListPushFront(ListNode* phead, LTDateType x)
{
//assert(phead);
//ListNode* first = phead->next;
//ListNode* newnode = BuyListNode(x);
//newnode->next = first;
//first->prev = newnode;
//newnode->prev = phead;
//phead->next = newnode;
ListInisert(phead->next, x);//复用替换
}
4.链表数据的删除
//删除指定位置数据
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
free(pos);
next->prev = prev;
prev->next = next;
}
//尾删
void ListPopBack(ListNode* phead)
{
//assert(phead);
//assert(phead->prev!= phead);
//ListNode* tail = phead->prev;
//ListNode* tailprev = tail->prev;
//free(tail);
//tailprev->next = phead;
//phead->prev = tailprev;
ListErase(phead->prev);//复用
}
//头删
void ListPopFront(ListNode* phead)
{
//assert(phead);
//assert(phead->prev != phead);
//ListNode* first = phead->next;
//ListNode* second = first->next;
//free(first);
//phead->next = second;
//second->prev = phead;
ListErase(phead->next);//复用
}
5.链表数据查找与判断链表是否为空
//查找
ListNode* ListFind(ListNode* phead, LTDateType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead)
{
if (cur->date == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//判断链表是否为空,为空返回0,不为空返回1
int ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead ? 0 : 1;
}
6.求链表长度及销毁链表
//求链表的长度
int ListSize(ListNode* phead)
{
assert(phead);
int size = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
//销毁链表
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
四 顺序表与链表的优缺点
1.顺序表优点:①支持按下标随机访问。②CPU高速缓存命中率比较高。(物理空间是连续的)
2.顺序表缺点:①空间不够需要增容。(一定的性能消耗),可能存在一定的空间浪费。②头部或者中间插入删除数据,需要挪动数据,效率较低。(O(N))。
3.双向带头循环链表优点:①按需要申请内存,需要存一个数据,就申请一块内存,也就不存在空间浪费。②任意位置O(1)时间内插入删除数据。
4.双向带头循环链表缺点:不支持带下标的随机访问。
5.总结:这两数据结构是相辅相成的,互相弥补对方的缺点,结合使用场景来进行选择使用。