一、双向链表
双向链表是一种线性链式存储结构,和单向链表的核心区别是:每个节点不仅保存指向下一个节点的指针(next),还保存指向上一个节点的指针(pre)
二、双向链表的基本操作
与单向链表相比较,双向链表的创建、插入、和删除与单向链表稍有区别,别的操作都是相同的,这里我将主要说明怎样创建、插入和删除。
类型定义:
cpp
typedef int DataType;
typedf struct node
{
DataType Data;
struct *pNext;
struct *pPre;
}
1.创建
与单向链表不同的是,双向链表保存两个指针,在创建好空白节点后,我们需要将pnext和ppre都置为空值。
关键代码示例:
cpp
Node_t *CreatEmptyDouList(void)
{
Node_t *pNewNode = NULL;
pNewNode = malloc(sizeof(Node_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return NULL;
}
pNewNode->pNext = NULL;
pNewNode->pPre = NULL;
return pNewNode;
}
2.插入
(1)头插法
- 申请节点空间pNewNode;
- 存放数据到pNewNode->pData空间;
- 将pNewNode的pNext成员赋值为空白节点的pNext;
- 将pNewNode的pPre成员赋值为空白节点的地址;
- 如果后续有节点,需将后面节点(pNewNode->pNext)的pPre指向新申请的节点
关键代码示例:
cpp
int InsertHeadDoulist(Node_t *pHead, DataType TmpData)
{
Node_t *pNewNode = NULL;
pNewNode = malloc(sizeof(Node_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return -1;
}
pNewNode->Data = TmpData;
pNewNode->pNext = pHead->pNext;
pNewNode->pPre = pHead;
pHead->pNext = pNewNode;
if(pNewNode->pNext != NULL)
{
pNewNode->pNext->pPre = pNewNode;
}
return 0;
}
(2)尾插法
关键代码示例:
- 定义一个指针pLastNode指向头指针;
- 遍历找到最后一个节点;
- 申请节点空间pNewNode;
- 存放数据到pNewNode->pData空间,并将pNewNode的pNext置空;
- 让pNewNode的pPre成员指向最后一个节点空间;
- 让最后一个节点空间的pNext成员指向新申请的节点空间。
cpp
int InsertTailDouList(Node_t *pHead, DataType TmpData)
{
Node_t *pNewNode = NULL;
Node_t *pLsatNode = NULL;
pLsatNode = pHead;
while(pLsatNode->pNext != NULL)
{
pLsatNode = pLsatNode->pNext;
}
pNewNode = malloc(sizeof(Node_t));
if(pNewNode == NULL)
{
perror("fail to malloc");
return -1;
}
pNewNode->Data = TmpData;
pNewNode->pNext = NULL;
pNewNode->pPre = pLsatNode;
pLsatNode->pNext = pNewNode;
return 0;
}
3.删除
- 遍历查找要删除的节点;
- 让pTmpNode前面节点的pNext指向pTmpNode的pNext;
- 如果后面有节点,让pTmpNode后面的节点的pPre指向pTmpNode的pPre;
- 定义一个指针pFreeNode指向pTmpNode,pTmpNode向后走;
- 释放pFreeNode,将pFreeNode置空
关键代码示例:
cpp
int DeleteDouListNode(Node_t *pHead, DataType TmpData)
{
int cnt = 0;
Node_t *pTmpNode = NULL;
Node_t *pFreeNode = NULL;
pTmpNode = pHead->pNext;
while(pTmpNode != NULL)
{
if(TmpData == pTmpNode->Data)
{
pTmpNode->pPre->pNext = pTmpNode->pNext;
if(pTmpNode->pNext != NULL)
{
pTmpNode->pNext->pPre = pTmpNode->pPre;
}
pFreeNode = pTmpNode;
pTmpNode = pTmpNode->pNext;
free(pFreeNode);
pFreeNode = NULL;
cnt++;
}
else
{
pTmpNode = pTmpNode->pNext;
}
}
return cnt;
}
三、双向循环链表
1.创建
与双向链表类似,注意pPre和pNext不为NULL,则是指向自己。
关键代码示例:
cpp
Node_t *CreatEmptyCirList(void)
{
Node_t *pNewNode = NULL;
pNewNode = malloc(sizeof(Node_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return NULL;
}
pNewNode->pPre = pNewNode;
pNewNode->pNext = pNewNode;
return pNewNode;
}
2.插入
(1)头插法
关键代码示例:
cpp
int InsertHeadCirList(Node_t *pHead, DataType TmpData)
{
Node_t *pNewNode = NULL;
pNewNode = malloc(sizeof(Node_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return -1;
}
pNewNode->Data = TmpData;
pNewNode->pNext = pHead->pNext;
pNewNode->pPre = pHead;
pNewNode->pPre->pNext = pNewNode;
pNewNode->pNext->pPre = pNewNode;
return 0;
}
(2)尾插法
关键代码示例:
cpp
int InsertTailCirList(Node_t *pHead, DataType TmpData)
{
Node_t *pNewNode = NULL;
pNewNode = malloc(sizeof(Node_t));
if(NULL == pNewNode)
{
perror("fail to malloc");
return -1;
}
pNewNode->Data = TmpData;
pNewNode->pNext = pHead;
pNewNode->pPre = pNewNode->pNext->pPre;
pNewNode->pNext->pPre = pNewNode;
pNewNode->pPre->pNext = pNewNode;
return 0;
}
图示:

3.遍历
遍历方法与双向链表基本一致,只是pTmpNode != pHead时进入循环遍历
关键代码示例:
cpp
nt ShowCirList(Node_t *pHead)
{
Node_t *pTmpNode = NULL;
pTmpNode = pHead->pNext;
while(pTmpNode != pHead)
{
printf("%d ",pTmpNode->Data);
pTmpNode = pTmpNode->pNext;
}
printf("\n");
return 0;
}
4.删除
- 定义pTmpNode指向第一个有效节点;
- 遍历查找要删除的节点;
- 找到目标节点时,前驱节点的pNext指向后继节点,后继节点的pPre指向前驱节点;
- 定义一个指针pFreeNode暂存待删节点,移动指针释放pFreeNode;
- 未找到目标节点时,继续向后遍历。
关键代码示例:
cpp
int DeleteCirList(Node_t *pHead, DataType TmpData)
{
Node_t *pTmpNode = NULL;
Node_t *pFreeNode = NULL;
int cnt = 0;
pTmpNode = pHead->pNext;
while(pTmpNode != pHead)
{
if(pTmpNode->Data == TmpData)
{
pTmpNode->pPre->pNext = pTmpNode->pNext;
pTmpNode->pNext->pPre = pTmpNode->pPre;
pFreeNode = pTmpNode;
pTmpNode = pTmpNode->pNext;
free(pFreeNode);
pFreeNode = NULL;
cnt++;
}
else
{
pTmpNode = pTmpNode->pNext;
}
}
return cnt;
}
5.销毁
与双向链表不同的是,定义两个指针从第一个有效节点开始,向后遍历释放空间,最后单独将头指针释放。
关键代码示例:
cpp
int DetoryCirList(Node_t **ppHead)
{
Node_t *pTmpNode = NULL;
Node_t *pFreeNode = NULL;
pTmpNode = pFreeNode = (*ppHead)->pNext;
while(pTmpNode != *ppHead)
{
pTmpNode = pTmpNode->pNext;
free(pFreeNode);
pFreeNode = pTmpNode;
}
free(*ppHead);
四、数组和链表的区别:
数组空间是连续的;链表空间可以是不连续的;
数组必须元素有限;链表元素是没有上限的;
数组插入和删除的效率低;链表插入和删除的效率高;
数组访问元素比较方便;链表访问元素不太方便。
五、栈
-
栈是一种先进后出,后进先出的数据结构
-
队列是一种先进先出,后进后出的数据结构
-
栈、队列和表的区别:
- 栈和队列是一种特殊的表状机构
- 栈和队列只能在指定的位置插入和删除
- 表可以在任意位置插入和删除
- 栈的构成:
- 栈顶:允许入栈和出栈的一端称为栈顶
- 栈底:不允许入栈和出栈的一端称为栈底
- 栈针:指向栈顶位置的指针或下标
- 栈的分类:
- 增栈:栈向高地址增长
- 减栈:栈向低地址增长
- 空栈:栈针指向入栈位置,称为空栈
- 满栈:栈针指向栈顶位置,称为满栈
- 分类:
- 空增栈
- 满增栈
- 空减栈
- 满减栈
- 入栈(压栈):将数据插入栈顶元素的位置
7.出栈(弹栈):将数据从栈顶元素位置取出;
六、顺序栈的实现
顺序栈是用顺序存储结构(数组 / 列表)实现的栈 ,栈本身是遵循 后进先出 规则的线性表,只能在栈顶进行插入(入栈)和删除(出栈)操作,栈底固定。
- 底层依赖连续的内存空间;
- 核心操作:入栈、出栈、取栈顶元素、判空、获取长度;