• 数据结构初阶--双向循环链表(讲解+类模板实现)


    带头双向链表的结构

    看下面的图,就是我今天要给大家分享有结构——带头双向循环链表。这里的头是不存放任何数据的,就是一个哨兵卫的头结点。

    用代码来表示每一个节点就是这样的:

    • 数据域和指针域
    • 两个指针,一个指向前驱结点,一个指向后继结点
    • 给定两个构造函数,有参和无参,分别对结点的指针域和数据域进行初始化
    template <class DateType> struct LinkNode { //数据域 DateType data; //两个指针 LinkNode<DateType>* prev; LinkNode<DateType>* next; LinkNode(DateType _data, LinkNode<DateType>* _prev = NULL, LinkNode<DateType>* _next = NULL) :data(_data), prev(_prev), next(_next){} LinkNode(LinkNode<DateType>* _prev = NULL, LinkNode<DateType>* _next = NULL) :prev(_prev), next(_next){} };

    带头双向链表的接口实现

    要实现的接口

    LinkList();//构造函数,初始化头节点 LinkList(const LinkList& list2);//拷贝构造,进行两个链表的拷贝 ~LinkList();//析构函数,用来清除链表,释放结点空间 int Length();//求双向循环链表的长度 void CreateList(int n);//常见n个结点的双向循环链表 bool GetElem(int pos,DateType& data);//得到pos位置结点的元素值 LinkNode* Locate(int i ,int back_pos);//定位元素,当back_pos=0的时候,从头节点向前查询第i个元素,back_pos!=0的时候,从头节点后查询第i个元素 bool Insert(int pos, const DateType& data, int back_pos);//在pos的位置插入元素,当back_pos!=0的时候,在pos位置后插入元素,当back_pos=0的时候,在pos位置前插入元素 void PrintList(int sign);//输出双向循环链表所有结点的元素值,当sign!=0时,正序打印元素值,当sign=0时,逆序打印 bool Delete(int pos, DateType& data,int back_pos);//删除pos位置的结点

    双向链表的小框架

    template<class DateType> class LinkList { public: private: LinkNode* head;//头节点 };

    初始化双向链表

    在初始化双链表的过程中,我们要开好一个头节点,作为哨兵卫的头节点,然后返回这个节点的指针,接口外面只要用一个节点指针接受这个返回值就好了,具体实现如下:

    //构造函数,初始化一个循环双链表 LinkList() { head = new LinkNode<DateType>; if (head == NULL) { cout << "内存分配失败" << endl; exit(-1); } head->data = 0; head->next = head; head->prev = head; }

    拷贝构造

    在拷贝构造中,要注意一件事情,就是最后一个结点的next需要指向头节点,头节点的prev需要指向最后一个结点,形成双向循环链表

    //拷贝构造 LinkList(const LinkList& list2) { LinkNode* p = list2.head->next; if (p == NULL) { cout << "内存分配失败" << endl; exit(-1); } head = new LinkNode; LinkNode* h = head; while (p!=list2.head) { LinkNode* t = new LinkNode; h->next = t; t->prev = h; t->data = p->data; p = p->next; h = h->next; } h->next = this->head; this->head->prev = h; }

    定位结点

    因为后面的在指定插入删除元素,需要定位pos位置结点的地址,所以这里旧封装一个函数,直接获取pos位置结点的地址

    //定位元素,back_pos=0时从头节点向前查第i个元素,d!=0时,从头节点向后找第i个元素 LinkNode* Locate(int i ,int back_pos) { if (head->next == head || i == 0) { return head; } int j = 0; LinkNode* p = head; //从头节点往后找第i个元素 if (back_pos) { while (p->next != head && j != i) { p = p->next; ++j; } if (p->next == head && j != i) { return NULL; } else { return p; } }//向前找 else { while (p->prev != head && j != i) { p = p->prev; ++j; } if (p->prev == head && j != i) return NULL; else return p; } }

    创建双向链表

    //创建双循环链表 void CreateList(int n) { DateType* nodetemp = new DateType[n]; for (rsize_t i = 0; i < n; i++) { cout << "Enter the element: " << endl; cin >> nodetemp[i]; Insert(i, nodetemp[i], 1); } delete[] nodetemp; }

    打印双向循环链表

    因为是双向循环链表,可以很简单的实现正序打印和逆序打印,所以这里用一个标志sign,来指定正序还是逆序打印链表元素

    //输出双循环链表所有结点的元素值,分为正序打印和逆序打印 void PrintList(int sign) { //正序打印 if (sign) { cout << "head " ; LinkNode<DateType>* p = head; while (p->next != head) { p = p->next; cout << "-> " << p->data; } cout << "->over" << endl; }//逆序打印 else { cout << "head " << endl; LinkNode<DateType>* p = head; while (p->prev != head) { p = p->prev; cout << "-> " << p->data; } cout << "->over" << endl; } }

    指定位置插入结点

    任意位置插入首先要开辟一个节点,然后就是按照所个位置,改变指针的指向来把这个节点连接上去,看具体代码实现如下:

    //在pos的位置插入元素 bool Insert(int pos, const DateType& data, int back_pos) { LinkNode* p = Locate(pos, back_pos); if (!p) return false; LinkNode* new_node = new LinkNode(data); if (NULL == new_node) { cout << "内存分配失败" << endl; exit(-1); } //p结点后插入 if (back_pos) { p->next->prev = new_node; new_node->prev = p; new_node->next = p->next; p->next = new_node; }//p结点前插入 else { p->prev->next = new_node; new_node->next = p; new_node->prev = p->prev; p->prev = new_node; }return true; }

    指定位置删除结点

    删除就要考虑链表是否为空,防止删除头节点

    //删除pos位置的结点 bool Delete(int pos, DateType& data,int back_pos) { LinkNode* p = Locate(pos, back_pos); if (!p) { return false; } if (p == head ) { cout << "请不要删除头节点" << endl; return false; } data = p->data; p->prev->next = p->next; p->next->prev = p->prev; delete p; return true; }

    获取链表长度

    int Length() { LinkNode* p = head; int i = 0; while (p->next != head) { ++i; p = p->next; } return i; }

    销毁链表

    在析构函数中实现链表的销毁

    //析构函数 ~LinkList() { LinkNode* p, * q = head->next; while (q != head) { p = q; q = q->next; delete p; } }

    整体代码以及测试

    #define _CRT_SECURE_NO_WARNINGS #include //引入头文件 #include//C++中的字符串 using namespace std; //标准命名空间 /* 双向循环链表 */ template <class DateType> struct LinkNode { //数据域 DateType data; //两个指针 LinkNode* prev; LinkNode* next; LinkNode(DateType _data, LinkNode* _prev = NULL, LinkNode* _next = NULL) :data(_data), prev(_prev), next(_next) {} LinkNode(LinkNode* _prev = NULL, LinkNode* _next = NULL) :prev(_prev), next(_next) {} }; template<class DateType> class LinkList { public: //构造函数,初始化一个循环双链表 LinkList() { head = new LinkNode; if (head == NULL) { cout << "内存分配失败" << endl; exit(-1); } head->data = 0; head->next = head; head->prev = head; } //拷贝构造 LinkList(const LinkList& list2) { LinkNode* p = list2.head->next; if (p == NULL) { cout << "内存分配失败" << endl; exit(-1); } head = new LinkNode; LinkNode* h = head; while (p!=list2.head) { LinkNode* t = new LinkNode; h->next = t; t->prev = h; t->data = p->data; p = p->next; h = h->next; } h->next = this->head; this->head->prev = h; } //析构函数 ~LinkList() { LinkNode* p, * q = head->next; while (q != head) { p = q; q = q->next; delete p; } } //求双循环链表的长度 int Length() { LinkNode* p = head; int i = 0; while (p->next != head) { ++i; p = p->next; } return i; } //创建双循环链表 void CreateList(int n) { DateType* nodetemp = new DateType[n]; for (rsize_t i = 0; i < n; i++) { cout << "Enter the element: " << endl; cin >> nodetemp[i]; Insert(i, nodetemp[i], 1); } delete[] nodetemp; } //得到位置为pos的结点元素值 bool GetElem(int pos,DateType& data) { LinkNode* p = head; if (pos<0 || pos>Length()) { cout << "输入的位置不合法" << endl; return false; } else { p = Locate(pos, 1); data = p->data; } return true; } //定位元素,back_pos=0时从头节点向前查第i个元素,d!=0时,从头节点向后找第i个元素 LinkNode* Locate(int i ,int back_pos) { if (head->next == head || i == 0) { return head; } int j = 0; LinkNode* p = head; //从头节点往后找第i个元素 if (back_pos) { while (p->next != head && j != i) { p = p->next; ++j; } if (p->next == head && j != i) { return NULL; } else { return p; } }//向前找 else { while (p->prev != head && j != i) { p = p->prev; ++j; } if (p->prev == head && j != i) return NULL; else return p; } } //在pos的位置插入元素 bool Insert(int pos, const DateType& data, int back_pos) { LinkNode* p = Locate(pos, back_pos); if (!p) return false; LinkNode* new_node = new LinkNode(data); if (NULL == new_node) { cout << "内存分配失败" << endl; exit(-1); } //p结点后插入 if (back_pos) { p->next->prev = new_node; new_node->prev = p; new_node->next = p->next; p->next = new_node; }//p结点前插入 else { p->prev->next = new_node; new_node->next = p; new_node->prev = p->prev; p->prev = new_node; }return true; } //输出双循环链表所有结点的元素值,分为正序打印和逆序打印 void PrintList(int sign) { //正序打印 if (sign) { cout << "head " ; LinkNode* p = head; while (p->next != head) { p = p->next; cout << "-> " << p->data; } cout << "->over" << endl; }//逆序打印 else { cout << "head " << endl; LinkNode* p = head; while (p->prev != head) { p = p->prev; cout << "-> " << p->data; } cout << "->over" << endl; } } //删除pos位置的结点 bool Delete(int pos, DateType& data,int back_pos) { LinkNode* p = Locate(pos, back_pos); if (!p) { return false; } if (p == head ) { cout << "请不要删除头节点" << endl; return false; } data = p->data; p->prev->next = p->next; p->next->prev = p->prev; delete p; return true; } private: LinkNode* head;//头节点 }; int main() { LinkList<int> list; list.CreateList(5); list.PrintList(1); cout << "-----------------------" << endl; LinkList<int> list2(list); list2.PrintList(1); cout << "-----------------------" << endl; list.Insert(0, 10, 1); list.PrintList(1); cout << list.Length() << endl; cout << "-----------------------" << endl; int b = 0; list.Delete(0, b, 1); cout << b << endl; list.PrintList(1); cout << list.Length() << endl; cout << "-----------------------" << endl; list.GetElem(3, b); cout << b << endl; cout <<"---------------------------" << endl; system("pause"); return EXIT_SUCCESS; }

    链表和顺序表的对比

    参考下表:

    不同点 顺序表 链表
    存储空间上 物理上连续 逻辑上连续
    随机访问 支持 不支持
    任意位置插入删除 要移动元素,O(N) 只要改变指针指向
    插入数据 要考虑扩容,会带来一定的空间消耗 没有容量这个概念,可以按需申请和释放
    缓存利用率
  • 相关阅读:
    CentOS7 离线安装 Zabbix5.0
    kubernetes(5) 续4
    如何基于surging跨网关跨语言进行缓存降级
    假设检验:正态性检验的那些bug——为什么对同一数据,normaltest和ktest会得到完全相反的结果?
    多线程与高并发基础
    安装 Windows Server 2019 VM虚拟机
    Ubuntu上安装部署k8s集群
    【算法题】小红书2023秋招提前批算法真题解析
    数据要素市场研究资料合集
    走进人工智能|自动驾驶 开启智能出行新时代
  • 原文地址:https://www.cnblogs.com/yzsn12138/p/16928086.html