《数据结构与算法》双向链表专题

1 双向链表的结构

带头双向循环链表

2c742cccf2734891820f41272d922c5a.png

 注意:这里的“带头”跟前面我们说的“头节点”是两个概念,实际前面在单链表阶段的称呼并不严谨,为了我们更好的理解就直接成为单链表的头节点。

带头链表的头节点实际为“哨兵位”,哨兵位节点不存储任何有效的元素,只是站在这里“放哨”的。

“哨兵位”存在的意义:遍历循环链表避免死循环。

当链表中只有哨兵位节点的时候,我们称该链表为空链表,即哨兵位是不能删除的。

2 双向链表的实现

List.h  定义双向链表的结构,双向链表要实现的接口/方法

List.c  具体实现双向链表里定义的接口/方法

test.c  测试双向链表

2.1 准备工作

List.h

#include<stdio.h>
#include<stdlib.h>
#inlcude<assert.h>

//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
  LTDataType data;
  struct ListNode* prev;    //指向前驱节点
  struct ListNode* next;    //指向后继节点
}LTNode;

LTNOde* LTBuyNode(LTDataType x)    //开辟新节点
{
  LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  if(newnode == NULL)
  {
    perror("malloc fail!!");
    exit(1);
  }
  newnode->data = x;
  newnode->next = nwenode->prev = newnode;

  return newnode;
}

List.c 

#include"List.h"

2.2 打印双向链表

List.h

void LTPrint(LTNode* phead);

List.c 

void LTPrint(LTNode* phead)
{
  assert(phead);    //phead不能为空
  LTNode* pcur = phead->next;
  while(pcur != phead)
  {
    printf("%d->", pcur->data);
    pcur = pcur->next;
  }
  printf("\n");
}

2.3 初始化双向链表

初始化有两种方式:

  • 一种是传参的(不用参数接收返回值)
  • 一种是不传参的(需要参数接收返回值)

两种的效果是一样

注意:双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位

List.h

//传参的初始化
void LInit01(LTNode** pphead);

//不传参的初始化
LTNode* LTInit02();

List.c 

//传参的初始化
void LInit01(LTNode** pphead)    //需要修改哨兵位,所以我们要传二级指针
{
  *pphead = LTNOde* LTBuyNode(-1)
}

//不传参的初始化
LTNode* LTInit02()
{
  LTNode* phead = LTNOde* LTBuyNode(-1);

  return phead;
}

 test.c

#inlcude"List.h"

//传参
void ListTest01()
{
  LTNode* plist = NULL;
  LTInit01(&plist);
}

//不传参
void ListTest02()
{
  LTNode* plist = LTInit();
}

int main()
{
  //ListTest01();
 ListTest02();
 
 return 0;
}
 

2.4 双向链表的尾插

我们先来思考一个问题:双向链表尾插需不需要找尾操作?

答案是:不需要,因为可通过头节点的前驱节点找到,即ptail = head->prev

尾插:在哨兵位之前插入节点/最后一个有效节点之后插入数据。

List.h

void LTPushBack(LTNode* phead, LTDataType x);

List.c 

void LTPushBack(LTNode* phead, LTDataType x)
{
  assert(phead);    //哨兵位不能为空,哨兵位为空就说明还没初始化链表
  LTNode* newnode = LTBuyNode(x);
  
  newnode->next = phead;
  newnode->prev = phead-prev;

  phead->prev->next = newnode;
  phead->prev = newnode;
}

test.c

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushBcak(plist,1);
  LTPushBcak(plist,2);
  LTPushBcak(plist,3);
  LTPushBcak(plist,4);

  LTPrint(plist);
}

int main()
{
  ListTest();
  return 0;
}

 输出结果为

1->2->3->4->

2.5 双向链表的头插

头插:在第一个有效节点之前插入。

List.h

void LTPushFront(LTNode* phead, LTDataType x);

List.c

void LTPushFront(LTNode* phead, LTDataType x)
{
  assert(phead);
  
  LTNode* newnode = LTBuuyNode(x);
  
  newnode->next = phead->next;
  newnode->prev = phead;

  phead->next->prev = newnode;
  phead->next = newnode;
}

test.c 

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushFront(plist,1);
  LTPushFront(plist,2);
  LTPushFront(plist,3);
  LTPushFront(plist,4);

  LTPrint(plist);
}

int main()
{
  ListTest();
  return 0;
}

输出结果为

4->3->2->1->

2.6 双向链表的尾删

List.h

void LTPopBack(LTNode* phead);

 List.c

void LTPopBack(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);    //链表为空:只有一个哨兵位节点

  LTNode* del = phead->prev;    //要删除的节点(尾节点)
  LTNode* prev = del->prev;    //要删除节点的前驱节点
  
  prev->next = phead;
  phead=>prev = prev;

  free(del);
  del = NULL;
}

 test.c

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushFront(plist,1);
  LTPushFront(plist,2);
  LTPushFront(plist,3);
  LTPushFront(plist,4);

  LTPrint(plist);

  LTPopBack(plist);
  LTPrint(plist);
  LTPopBack(plist);
  LTPrint(plist);
  LTPopBack(plist);
  LTPrint(plist);
}

int main()
{
  ListTest();
  return 0;
}

 输出结果为

4->3->2->1->
4->3->2->
4->3->
4->

2.7 双向链表的头删 

List.h 

void LTPopFront(LTNode* phead);

 List.c

void LTPopFront(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);  

  LTNode* del = phead->next;    //要删除的节点(第一个节点)
  LTNode* next = del->next;    //要删除节点的后继节点

  next->prev = phead;
  phead->next = next;

  free(del);
  del = NULL;
}

 test.c

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushFront(plist,1);
  LTPushFront(plist,2);
  LTPushFront(plist,3);
  LTPushFront(plist,4);

  LTPrint(plist);

  LTPopFront(plist);
  LTPrint(plist);
  LTPopFront(plist);
  LTPrint(plist);
  LTPopFront(plist);
  LTPrint(plist);
}

int main()
{
  ListTest();
  return 0;
}

输出结果为

4->3->2->1->
3->2->1->
2->1->
1->

2.8 查找双向链表

List.h

LTNode* LTFind(LTNode* phead, LTDataType x);

List.c

LTNode* LTFind(LTNode* phead, LTDataType x)
{
  assert(phead);
  LTNode* pcur = phead->next;
  while(pcur != phead)
  {
    if(pcur->data == x)
    {
      return pcur;
    }
    pcur = pcur->next;
  }
  return NULL;
}

test.c

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushFront(plist,1);
  LTPushFront(plist,2);
  LTPushFront(plist,3);
  LTPushFront(plist,4);

  LTPrint(plist);

  LTNode* findRet01 = LTFind(plist, 4);
  if(findRet01 == NULL)
  {
    printf("未找到!!\n");
  }
  else
  {
  printf("找到啦!!\n");
  }

  LTNode* findRet02 = LTFind(plist, 520);
  if(findRet02 == NULL)
  {
    printf("未找到!!\n");
  }
  else
  {
  printf("找到啦!!\n");
  }
}

int main()
{
  ListTest();
  return 0;
}

输出结果为

4->3->2->1->
找到啦!!
未找到!!

2.9 指定位置之后插入数据 

List.h 

void LTInsert(LTNode* pos, LTDataType x);

 List.c

void LTInsert(LTNode* pos, LTDataType x)
{
  assert(pos);
  LtNode* newnode = LTBuyNode(x);
  
  newnode->next = pos->next;
  newnode->prev = pos;
  
  pos->next->prev = newnode;    //注意顺序
  pos->next = newnode;
}

 test.c

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushFront(plist,1);
  LTPushFront(plist,2);
  LTPushFront(plist,3);
  LTPushFront(plist,4);

  LTPrint(plist);

  LTNode* findRet = LTFind(plist,1);
  LTInsert(findRet, 66);
  LTPrint(plist);
}

int main()
{
  ListTest();
  return 0;
}

输出结果为

4->3->2->1->
4->3->2->1->66->

2.10 删除指定位置数据

List.h 

void LTErase(LTNode* pos);

List.c

void LTErase(LTNode* pos)
{
  assert(pos);

  pos->next->prev = pos->prev;
  pos->prev->next = pos->next;

  free(pos);
  pos = NULL;
}

test.c

void ListTest()
{
  LTNode* plist = LTInit();
  LTPushFront(plist,1);
  LTPushFront(plist,2);
  LTPushFront(plist,3);
  LTPushFront(plist,4);

  LTPrint(plist);

  LTNode* findRet = LTFind(plist,3);
  LTErase(findRet);
  LTPrint(plist);
}

int main()
{
  ListTest();
  return 0;
}

输出结果为

4->3->2->1->
4->2->1->

2.11 销毁双向链表

可以有两种销毁方法

  • 传二级指针,调用完之后自动对哨兵位置NULL
  • 传一级指针,调用完要手动置NULL(推荐这种,因为可以保持接口一致性)

List.h

//传二级指针
void LTDesTory01(LTNode** pphead);
//传一级指针
void LTDesTory01(LTNode* phead);

 List.c

//传二级指针
void LTDesTory(LTNode** pphead)
{
  assert(pphead);
  //哨兵位不能为空
  assert(*pphead);

  LTNode* pcur = (*pphead)->next;
  while(pcur != *pphead)
  {
    LTNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  //链表中只有一个哨兵位
  free(*pphead);
  *pphead = NULL;
}

//传一级指针
void LTDesTory(LTNode* phead)
{
  //哨兵位不能为空
  assert(phead);

  LTNode* pcur = phead->next;
  while(pcur != phead)
  {
    LTNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  //链表中只有一个哨兵位
  free(phead);
  phead = NULL;
}

3 整理双链表代码

List.h

#include<stdio.h>
#include<stdlib.h>
#inlcude<assert.h>

//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
  LTDataType data;
  struct ListNode* prev;    //指向前驱节点
  struct ListNode* next;    //指向后继节点
}LTNode;

//注意,双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位

LTNOde* LTBuyNode(LTDataType x)    //开辟新节点
{
  LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  if(newnode == NULL)
  {
    perror("malloc fail!!");
    exit(1);
  }
  newnode->data = x;
  newnode->next = nwenode->prev = newnode;

  return newnode;
}

//打印
void LTPrint(LTNode* phead);

//初始化
//传参的初始化
void LInit01(LTNode** pphead);
//不传参的初始化
LTNode* LTInit02();

//尾插
void LTPushBack(LTNode* phead, LTDataType x);

//头插
void LTPushFront(LTNode* phead, LTDataType x);

//尾删
void LTPopBack(LTNode* phead);

//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

//指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除指定位置数据
void LTErase(LTNode* pos);

//销毁
//传二级指针
void LTDesTory01(LTNode** pphead);
//传一级指针
void LTDesTory01(LTNode* phead);

List.c

#include"List.h"

//打印
void LTPrint(LTNode* phead)
{
  assert(phead);    //phead不能为空
  LTNode* pcur = phead->next;
  while(pcur != phead)
  {
    printf("%d->", pcur->data);
    pcur = pcur->next;
  }
  printf("\n");
}

//初始化
//传参的初始化
void LInit01(LTNode** pphead)    //需要修改哨兵位,所以我们要传二级指针
{
  *pphead = LTNOde* LTBuyNode(-1)
}

//不传参的初始化
LTNode* LTInit02()
{
  LTNode* phead = LTNOde* LTBuyNode(-1);

  return phead;
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
  assert(phead);    //哨兵位不能为空,哨兵位为空就说明还没初始化链表
  LTNode* newnode = LTBuyNode(x);
  
  newnode->next = phead;
  newnode->prev = phead-prev;

  phead->prev->next = newnode;
  phead->prev = newnode;
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
  assert(phead);
  
  LTNode* newnode = LTBuuyNode(x);
  
  newnode->next = phead->next;
  newnode->prev = phead;

  phead->next->prev = newnode;
  phead->next = newnode;
}

//尾删
void LTPopBack(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);    //链表为空:只有一个哨兵位节点

  LTNode* del = phead->prev;    //要删除的节点(尾节点)
  LTNode* prev = del->prev;    //要删除节点的前驱节点
  
  prev->next = phead;
  phead=>prev = prev;

  free(del);
  del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
  assert(phead);
  assert(phead->next != phead);  

  LTNode* del = phead->next;    //要删除的节点(第一个节点)
  LTNode* next = del->next;    //要删除节点的后继节点

  next->prev = phead;
  phead->next = next;

  free(del);
  del = NULL;
}

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
  assert(phead);
  LTNode* pcur = phead->next;
  while(pcur != phead)
  {
    if(pcur->data == x)
    {
      return pcur;
    }
    pcur = pcur->next;
  }
  return NULL;
}

//指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
  assert(pos);
  LtNode* newnode = LTBuyNode(x);
  
  newnode->next = pos->next;
  newnode->prev = pos;
  
  pos->next->prev = newnode;    //注意顺序
  pos->next = newnode;
}

//删除指定位置数据
void LTErase(LTNode* pos)
{
  assert(pos);

  pos->next->prev = pos->prev;
  pos->prev->next = pos->next;

  free(pos);
  pos = NULL;
}

//销毁
//传二级指针
void LTDesTory(LTNode** pphead)
{
  assert(pphead);
  //哨兵位不能为空
  assert(*pphead);

  LTNode* pcur = (*pphead)->next;
  while(pcur != *pphead)
  {
    LTNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  //链表中只有一个哨兵位
  free(*pphead);
  *pphead = NULL;
}

//传一级指针
void LTDesTory(LTNode* phead)
{
  //哨兵位不能为空
  assert(phead);

  LTNode* pcur = phead->next;
  while(pcur != phead)
  {
    LTNode* next = pcur->next;
    free(pcur);
    pcur = next;
  }
  //链表中只有一个哨兵位
  free(phead);
  phead = NULL;
}

4 顺序表和双向链表的优缺点

24f5918228574bb290a0a85700c9bf1e.png

 

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值