【数据结构】双链表

双链表

结构描述:
#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 @ 2024-07-18 20:48  codels  阅读(31)  评论(0)    收藏  举报