【数据结构】双链表

双链表

结构描述:
#include <iostream>
#include <cstdlib>
using namespace std;
typedef int DataType;
//链表节点
typedef struct Node {
DataType A;
struct Node * Prev;
struct Node * Next;
}Node;
class DoubleLinkedList {
private:
Node * Head;
public:
//尾插
void PushBack(DataType X);
//头插
void PushFront(DataType X);
//在第Pos个位置插入,若不存在该位置报错
void InsertThisPos(int Pos, DataType X);
//尾删
void PopBack();
//头删
void PopFront();
//删除第Pos位元素,若无该位置,则返回错误
void DeleteThisPos(int Pos);
// 把链表置为空表
void MakeEmpty();
//查找值为 Target的元素,若不存在,则返回空指针,存在则返回节点指针
Node * SearchByValue(DataType Target);
void ModifyByValue(DataType Value, DataType NewValue);
//创建一个空表
void Init();
//判空,若空,则返回true,反之返回false
bool IsEmpty();
//打印链表
void Print();
//分配空间,根据参数X分配空间,分配成功则返回该节点的指针
Node * BuyNode(DataType X);
};

初始化

把 头节点 Head 指向空

void DoubleLinkedList::Init() {
Head = nullptr;
}

判空:

Head 指向空,即链表为空。

bool DoubleLinkedList::IsEmpty() {
return Head == nullptr;
}

分配节点

使用 malloc() 函数分配空间,为节点的各个域赋值。

Node * DoubleLinkedList::BuyNode(DataType X) {
Node * NewNode = (Node *)malloc(sizeof (Node));
if (NewNode == nullptr) {
cout << "Malloc Failed!\n";
exit(-1);
}
NewNode->A = X;
NewNode->Prev = nullptr;
NewNode->Next = nullptr;
return NewNode;
}

头插

方法原型: void PushFront(DataType X)

功能:根据 X 分配节点,并把该节点设置为链表的首个元素

  • 若表空:直接头指针 Head 指向新节点 NewNode
  • 表非空:
    1. NewNodeNext 指针域指向头指针 Head
    2. HeadPrev 指针域指向 NewNode
    3. Head 指向 NewNode

图示:

image-20240718155402192

void DoubleLinkedList::PushFront(DataType X) {
//表空
if (IsEmpty()) {
Node * NewNode = BuyNode(X);
Head = NewNode;
}
//表非空
else {
Node * NewNode = BuyNode(X);
NewNode->Next = Head;
Head->Prev = NewNode;
Head = NewNode;
}
}

头删

  • 表空:报错

  • 非空:删除第一个元素

    • 当链表只有一个元素时:

      • 直接释放

      img

    • 当链表有多个元素时:

      1. 用临时变量 Tmp 保存头指针,头指针向后移动一位 Head = Head->Next
      2. 把当前头指针指向的节点的 Pre 域置空;
      3. 释放 Tmp

      img

void DoubleLinkedList::PopFront() {
if (IsEmpty()) {
cout << "List Is Empty!\n";
exit(-1);
}
//非空
//只有一个元素时
if (Head->Next == nullptr) {
free(Head);
Head = nullptr;
}
//有多个元素时
else {
//临时变量保存
Node * Tmp = Head;
//头指针后移一位
Head = Head->Next;
//当前头指针的 Prev域置空
Head->Prev = nullptr;
//释放原来的头指针指向的节点
free(Tmp);
Tmp = nullptr;
}
}

插入

方法:void InsertThisPos(int Pos, DataType X);

功能:在第 Pos (Pos >= 0) 个位置插入元素(从0开始计数)。

  • Pos == 0 直接调用头插方法

  • Pos 不为0,判断 Pos 是否存在:

  • 存在:插入元素

  • 否则:返回错误

利用双指针,让控制循环的变量 iPreCur 一起向后走,直到 i == Pos 或者 PreCur->Next == nullptr 停下。此时若 i == Pos && PreCur->Next != nullptr,由说明该位置可以插入元素,否则不可插入。

其中,表尾是一个特殊的情况,一般表中间插入会将插入节点 NewNode 的后继节点的 Prev 指针指向 NewNode,但是在表尾插入,NewNode 的下一位是空指针,无法进行这种操作。

void DoubleLinkedList::InsertThisPos(int Pos, DataType X) {
if (Pos < 0) {
cout << Pos << " Posiont is not exist!\n";
exit(-1);
}
//表头插入
if (Pos == 0) {
PushFront(X);
}
else {
int i;
//要插入位置的前一位
Node * PreCur;
for (i = 1, PreCur = Head; i < Pos && PreCur != nullptr; i++, PreCur = PreCur->Next) {
;
}
//表尾插入
if (i == Pos && PreCur->Next == nullptr) {
Node * NewNode = BuyNode(X);
//设置新节点的指针域
NewNode->Next = PreCur->Next;
NewNode->Prev = PreCur;
//修改节点之间的关系
NewNode->Prev->Next = NewNode;
//这一步在表尾插入时无法使用,对一个空指针取值无意义。
//// NewNode->Next->Prev = NewNode;
}
//表中间插入
else if (Pos == i && PreCur != nullptr) {
Node * NewNode = BuyNode(X);
//设置新节点的指针域
NewNode->Next = PreCur->Next;
NewNode->Prev = PreCur;
//修改节点之间的关系
NewNode->Prev->Next = NewNode;
NewNode->Next->Prev = NewNode;
}
else {
cout << Pos << " Posiont is not exist!\n";
exit(-1);
}
}
}

删除

  • 表空:报错
  • 非空:
void DoubleLinkedList::DeleteThisPos(int Pos, DataType X) {
if (Pos < 0) {
cout << Pos << " Posiont is not exist!\n";
exit(-1);
}
//删除首个节点
if (Pos == 0) {
PopFront(X);
}
else {
int i;
//要删除的节点
Node * Cur;
//Cur->Next == nullptr则说明Cur是末尾元素
for (i = 1, Cur = Head->Next; Cur->Next != nullptr && i < Pos; i++, Cur = Cur->Next) {
;
}
//是最后一位元素
if (i == Pos && Cur->Next == nullptr) {
Node * Tmp = Cur;
//把倒数第二位元素向空指针,即把倒数第二位元素变成末尾元素
Cur->Prev->Next = Cur->Next;
//释放
free(Tmp);
Tmp = nullptr;
}
//非末尾元素
else if (i == Pos && Cur->Next != nullptr) {
Node * Tmp = Cur;
//设置节点关系
Cur->Prev->Next = Cur->Next;
//非末尾元素多这一步,把后继的Prev指针重新设置位置
Cur->Next->Pre = Cur->Pre;
//释放
free(Tmp);
Tmp = nullptr;
}
else {
cout << Pos << " Posiont is not exist!\n";
exit(-1);
}
}
}

尾插

  • 表空:直接把头指针指向新节点 NewNode
  • 非空:遍历至表尾,在末尾接入 NewNode,并设置好其指针域
void DoubleLinkedList::PushBack(DataType X) {
if (IsEmpty()) {
Node * NewNode = BuyNode(X);
Head = NewNode;
}
else {
Node * NewNode = BuyNode(X);
Node * Cur = Head;
//Cur->Next == nullptr时退出循环,即Cur是末尾节点时退出循环
while (Cur->Next != nullptr) {
Cur = Cur->Next;
}
//设置新节点的指针域
NewNode->Next = Cur->Next;
NewNode->Prev = Cur;
//在末尾插入该节点
Cur->Next = NewNode;
}
}

尾删

  • 表空:报错

  • 非空:

    • 只有一个元素:直接释放

    • 有多个元素时:遍历至表尾,释放

      img

void DoubleLinkedList::PopBack() {
if (IsEmpty()) {
cout << "List Is Empty!\n";
exit(-1);
}
//只有一个元素时
if (Head->Next == nullptr) {
free(Head);
Head = nullptr;
}
else {
//有多个元素时,遍历到末尾,删除末尾元素。
Node * Cur = Head;
while (Cur->Next != nullptr) {
Cur = Cur->Next;
}
Cur->Prev->Next = Cur->Next;
free(Cur);
Cur = nullptr;
}
}
根据值查找元素
  • 表空:返回错误
  • 非空:遍历链表,逐一比较,直到第一个与目标值相等的元素时,返回该节点的指针。若直至表尾仍未发现该元素,则返回一个空指针。
Node * DoubleLinkedList::SearchByValue(DataType Target) {
if (IsEmpty()) {
cout << "List Is Empty!\n";
exit(-1);
}
Node * Cur = Head;
while (Cur != nullptr) {
if (Cur->A == Target) {
return Cur;
}
Cur = Cur->Next;
}
return nullptr;
}
修改链表元素值
  • 查找:
    • 表空:返回错误
    • 非空:找到返回,找不到返回空
  • 修改:根据查找到的结果修改;
    • 查找返回空:给出提示消息,并退出程序
    • 查找返回元素节点指针:修改该元素的值
void DoubleLinkedList::ModifyByValue(DataType Target, DataType NewValue) {
Node * Ret = SearchByValue(Target);
if (Ret == nullptr) {
cout << "The Target Is Not Exist!\n";
exit(-1);
}
Ret->A = NewValue;
}

置空

  • 表空:报错
  • 非空:一直头删,直至表空
void DoubleLinkedList::MakeEmpty() {
if (IsEmpty()) {
cout << "List Is Empty!\n";
exit(-1);
}
while (!IsEmpty()) {
cout << Head->A << " ";
PopFront();
cout << "Has Been Poped!\n";
}
cout << "Make Empty Successfully!\n";
}

踩坑

双链表插入操作应该先考虑头和尾。不考虑尾节点的话,该节点为尾节点,但是却要进行NewNode->Next->Pre = NewNode 的操作,这样对空指针来说是不被允许的。

考虑插入删除第 Pos 个位置时,一定保证 Pos 是非负数。

posted @   codels  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示