上一边文章我们介绍了单链表及循环链表,现在我们了解一下双向链表及双向循环链表。
1. 双向链表
单链表中,每个结点有一个指向其直接后继结点的指针,整个单链表只能沿着一个方向操作,那么双向链表,顾名思义,在它的结点中,不仅包含直接后继结点的地址指针,还包含它的直接前驱结点地址指针。
结点定义:
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
ElemType data;
struct Node *prior;
struct Node *next;
}Node;
typedef struct Node * LinkList;
结点结构如下:
了解了结点结构后,我们看一下非空链表的结构图:
带头结点:
不带头结点:
1.1 双向链表创建
双向链表的创建,我们默认创建带头结点的链表。
思路:
- 开辟空间,创建头结点,并且判断是否创建成功。
- 将头结点前驱指针设置为NULL,后继指针设置为NULL。
- 通过循环依次创建结点,并追加到链表末尾。
- 返回成功。
代码实现如下:
//创建双向链表
Status createLinkList(LinkList *L){
//*L 指向头结点
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) return ERROR;
(*L)->prior = NULL;
(*L)->next = NULL;
(*L)->data = -1;
//新增数据
LinkList p = *L;
for(int i=0; i < 10;i++){
//1.创建1个临时的结点,并设置初始值。
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->prior = NULL;
temp->next = NULL;
temp->data = i;
//2.为新增的结点建立双向链表关系
//temp 是p的后继
p->next = temp;
//temp 的前驱是p
temp->prior = p;
//p要记录最后的结点的位置,方便下一次插入
p = p->next;
}
return OK;
}
1.2 双向链表插入结点
思路:
- 创建新结点temp,并进行初始化。
- 遍历查找插入位置结点的前驱结点位置。
- 如果插入的位置大于链表的总长度,返回ERROR。
- 判断是否插入在尾部,如果是,只需修改尾部结点与插入结点的关系。
- 如果不是插在尾部,则需修改插入结点与其前驱结点和后继结点的关系。
- 返回OK。
代码实现如下:
//双向链表插入结点
Status ListInsert(LinkList *L, int i, ElemType data){
//1. 插入的位置不合法 为0或者为负数
if(i < 1) return ERROR;
//2. 新建结点
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->data = data;
temp->prior = NULL;
temp->next = NULL;
//3.将p指向头结点!
LinkList p = *L;
//4. 找到插入位置i直接的结点
for(int j = 1; j < i && p;j++)
p = p->next;
//5. 如果插入的位置超过链表本身的长度
if(p == NULL){
return ERROR;
}
//6. 判断插入位置是否为链表尾部;
if (p->next == NULL) {
p->next = temp;
temp->prior = p;
}else
{
//1 将p->next 结点的前驱prior = temp
p->next->prior = temp;
//2 将temp->next 指向原来的p->next
temp->next = p->next;
//3 p->next 更新成新创建的temp
p->next = temp;
//4 新创建的temp前驱 = p
temp->prior = p;
}
return OK;
}
1.3 双向链表删除指定结点
思路:
- 遍历查找删除位置结点的前驱结点位置,创建结点指针p指向该前驱结点。。
- 如果插入的位置小于1或者大于链表的总长度,返回ERROR。
- 创建结点指针delTemp指向待删除结点,修改结点p的next指向delTemp的next。
- 如果待删除结点delTemp有后继结点,那么修改其后继结点的prior指向p。
- 释放delTemp结点。
- 返回OK。
代码实现如下:
//删除双向链表指定位置上的结点
Status ListDelete(LinkList *L, int i, ElemType *e){
int k = 1;
LinkList p = (*L);
//1.判断双向链表是否为空,如果为空则返回ERROR;
if (*L == NULL) {
return ERROR;
}
//2. 将指针p移动到删除元素位置前一个结点。
while (k < i && p != NULL) {
p = p->next;
k++;
}
//3.处理前后边界条件,如果k>i 或者 p == NULL 则返回ERROR
if (k>i || p == NULL) {
return ERROR;
}
//4.创建临时指针delTemp指向要删除的结点,并将要删除的结点的data赋值给*e,带回到main函数
LinkList delTemp = p->next;
*e = delTemp->data;
//5. p->next等于要删除的结点的下一个结点
p->next = delTemp->next;
//6. 如果删除结点的下一个结点不为空,则将将要删除的下一个结点的前驱指针赋值p;
if (delTemp->next != NULL) {
delTemp->next->prior = p;
}
//7.删除delTemp结点
free(delTemp);
return OK;
}
1.4 双向链表删除指定元素
思路:
- 创建结点指针p,并指向头结点。
- 遍历双向链表,查找与指定值相等的结点p,即待删除结点p。
- 若待删除结点p存在,修改结点p的前驱结点的后继指针,若p有后继结点,修改结点p的后继结点的前驱指针。
- 返回OK。
代码实现如下:
//删除双向链表指定的元素
Status LinkListDeletVAL(LinkList *L, int data){
//1.判断双向链表是否为空,如果为空则返回ERROR;
if (*L == NULL) {
return ERROR;
}
LinkList p = *L;
//2.遍历双向链表
while (p) {
//2.判断当前结点的数据域和data是否相等,若相等则删除该结点
if (p->data == data) {
//修改被删除结点的前驱结点的后继指针
p->prior->next = p->next;
//修改被删除结点的后继结点的前驱指针
if(p->next != NULL){
p->next->prior = p->prior;
}
//释放被删除结点p
free(p);
//退出循环
break;
}
//没有找到该结点,则继续移动指针p
p = p->next;
}
return OK;
}
1.5 双向链表查找元素位置
思路:
- 创建结点指针p,并指向首元结点,创建索引i,并设置为1.
- 遍历双向链表,查找与指定指相当的结点p。
- 如果找到,则返回索引i。
- 如果未找到,返回-1。
代码实现如下:
//在双向链表中查找元素位置
int selectElem(LinkList L,ElemType elem){
LinkList p = L->next;
int i = 1;
while (p) {
if (p->data == elem) {
return i;
}
i++;
p = p->next;
}
return -1;
}
1.6 双向链表更新结点数据
思路:
- 创建结点指针p,并指向首元结点。
- 遍历双向链表,查找待更新结点p。
- 重新赋值p->next。
- 返回OK。
代码实现如下:
Status replaceLinkList(LinkList *L,int index,ElemType newElem){
if (index < 1) {
return ERROR;
}
LinkList p = (*L)->next;
for (int i = 1; i < index && p->next != NULL; i++) {
p = p->next;
}
p->data = newElem;
return OK;
}
2. 双向循环链表
上面了解了双向链表,那么双向循环链表,顾名思义,就是就是将双向链表尾部结点和头结点连接起来,如下图:
空双向循环链表:
非空双向循环链表:
2.1 双向循环链表创建
思路:
- 开辟空间,创建头结点,并且判断是否创建成功。
- 将头结点前驱指针设置为头结点,后继指针设置为头结点,形成一个闭环。
- 通过循环依次创建结点,并追加到链表末尾。
- 返回成功。
代码实现如下:
// 双向循环链表初始化
Status creatLinkList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) {
return ERROR;
}
(*L)->next = (*L);
(*L)->prior = (*L);
//新增数据
LinkList p = *L;
for(int i=0; i < 10;i++){
//1.创建1个临时的结点
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->data = i;
//2.为新增的结点建立双向链表关系
//temp是p的后继
p->next = temp;
//temp的前驱是p
temp->prior = p;
//temp的后继是*L
temp->next = (*L);
//(*L)的前驱是新建的temp
(*L)->prior = temp;
//p要记录最后的结点的位置,方便下一次插入
p = p->next;
}
return OK;
}
2.2 双向循环链表插入结点
思路:
- 创建结点指针p,并指向头结点。
- 遍历查找到插入位置前一个结点p。
- 创建新增节点temp,并赋值。
- 修改temp结点的前后关系。
①. 将结点temp的前驱结点设置为p。
②. 将结点temp的后继结点设置为p->next。
③. 将结点p的后继结点设置为temp。
④. 将结点temp的后继结点的前驱结点设置为temp。 - 返回OK。
代码实现如下:
//双向循环链表插入元素
/*当插入位置超过链表长度则插入到链表末尾*/
Status LinkListInsert(LinkList *L, int index, ElemType e){
//1.如果i>index 则返回error
if (index < 1) return ERROR;
//2. 创建指针p,指向双向链表头
LinkList p = (*L);
int i = 1;
//3.双向循环链表为空,则返回error
if(*L == NULL) return ERROR;
//4.找到插入前一个位置上的结点p
while (i < index && p->next != *L) {
p = p->next;
i++;
}
//5.创建新结点temp
LinkList temp = (LinkList)malloc(sizeof(Node));
//6.temp 结点为空,则返回error
if (temp == NULL) return ERROR;
//7.将生成的新结点temp数据域赋值e.
temp->data = e;
//8.将结点temp 的前驱结点为p;
temp->prior = p;
//9.temp的后继结点指向p->next;
temp->next = p->next;
//10.p的后继结点为新结点temp;
p->next = temp;
//11.temp节点的下一个结点的前驱为temp结点
temp->next->prior = temp;
return OK;
}
2.3 双向循环链表删除结点
思路:
- 判断链表是否存在,不存在则return。
- 如果删除到只剩下首元结点了,则直接将*L置空。
- 创建结点指针,并指向首元结点,遍历查找到删除的结点temp,并将删除结点的值赋值给*e。
- 修改链表连接关系。
①. 修改被删除结点temp的前驱结点的后继指针。
②. 修改被删除结点temp的后继结点的前驱指针。 - 释放删除的结点temp。
- 返回OK。
代码实现如下:
// 双向循环链表删除结点
Status LinkListDelete(LinkList *L,int index,ElemType *e){
int i = 1;
LinkList temp = (*L)->next;
if (*L == NULL) {
return ERROR;
}
//如果删除到只剩下首元结点了,则直接将*L置空;
if(temp->next == *L){
free(*L);
(*L) = NULL;
return OK;
}
//1.找到要删除的结点
while (i < index) {
temp = temp->next;
i++;
}
//2.给e赋值要删除结点的数据域
*e = temp->data;
//3.修改被删除结点的前驱结点的后继指针
temp->prior->next = temp->next;
//4.修改被删除结点的后继结点的前驱指针
temp->next->prior = temp->prior;
//5. 删除结点temp
free(temp);
return OK;
}
2.4 双向循环链表遍历
思路:
- 判断链表是否为空,若为空,直接return。
- 创建结点指针temp,并指向首元结点。
- 循环遍历temp,若temp不为头结点,输出temp结点值,若temp为头结点,链表已经遍历到结尾,退出循环。
代码实现如下:
//遍历双向循环链表
Status Display(LinkList L){
if (L == NULL) {
printf("打印的双向循环链表为空!\n\n");
return ERROR;
}
printf("双向循环链表内容: ");
LinkList p = L->next;
while (p != L) {
printf("%d ",p->data);
p = p->next;
}
printf("\n\n");
return OK;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。