在C++里,存在多种类型的表,其中有一种线性表,链表则是一种线性表,正如它的名字一样,链表的样子就像是用一条链子串起来的表(这里,我主要讲的是双循环链表)
而用来连接链子的每个环节的是指针,在最基本的单向不循环链表中,有一根单向指针next,这根指针的作用显而易见,就是串联上下两个节点。
说到串联节点,那么必不可少的自然是节点,节点又如何创建呢。首先,我们要创建一个节点类(class Node),在双循环链表中,想要实现双循环,那么就要求要有双向效果,也就是用来指向的指针应该要有两根,一根指向本节点的下一个节点(next),一根指向本节点的上一个节点(prior),以此达到双向的效果
这里,我以一个记录学生信息的双循环链表作为例子
class Node
{
public:
int ID;
string name;
Node * next;
Node * prior;
Node() :next(nullptr), prior(nullptr) {}
Node(int ID, string name) :ID(ID), name(name), next(nullptr), prior(nullptr) {}
Node(Node &n) :ID(n.ID), name(n.name), next(nullptr), prior(nullptr) {}
void display()
{
cout << "ID:" << ID << endl << "Name:" << name << endl;
}
};
在这个类里面,要有三种构造函数,无参构造主要是为了在链表类里面创建头结点使用,第一类有参构造是为了主函数内创建节点,而第二类构造则是拷贝构造,目的在于拷贝节点信息。
创建完节点,需要做的便是将节点串联起来。串联节点,首先需要有一个起点,这个起点就是一个标志性作用,在循环链表里面,头结点既是开头也是结尾,起到关键作用
所以,在创建链表的时候,首先应该先有一个Node型的头结点
Link::Link()
{
head = new Node();
head->name = "头结点";
head->next = head;
head->prior = head;
}
在没有插入任何其他节点的时候,头结点既是头节点的上一节点也是头结点的下一节点,也就是所有指针全部指向自己。当然,这个头结点是private的,不可被外界访问到。有了头节点之后,我们可以开始对这个链表进行各种操作了。
class Link
{
private:
Node * head;
public:
Link();
~Link();
void traverse1();
void traverse2();
void insert(Node &n);
void deleteByID(int id);
void modifyByID(int id, string name);
};
这里,我只写了顺序遍历、倒序遍历、插入、按ID关键字删除、 按关键字修改信息五个函数。首先,我们先讲插入,没有插入就没有其他节点的存在。双循环链表的插入比较复杂一点,最好可以用纸笔进行模拟,可以促进理解。
插入,有两种情况,第一种是在除了头结点外为空的链上插入,此时我们要做的是将头节点的next指向要插入的节点(下面称为本节点),将头结点的prior指向本节点,将本节点的prior指向头结点,将本节点的next指向头结点。这样就可以实现头结点与本节点之间的双向循环。至于第二种,就是在链表已经足够长的情况下插入节点,我们这里的插入只是单纯插在尾部,至于其他类型的插入,大致不会差多少,所以谨以此例子作为示范。要插在尾部,我们要先找到尾部在哪里,于是有了一根指针(tail),tail=head,当tail的下一个节点不是头结点的时候,我们就继续找下一个,一直到他指向头结点,此时tail所代表的节点必然就是最后一个节点,new 出一个新的实体,这个实体的所有信息与传入的节点的信息完全一致,也就是进行拷贝,然后使newNode得next指向头结点,将头结点的prior指向newNode,将newNode的prior指向tail(最后一个节点),将最后一个节点的next指向newNode,这样,newNode就成为新的最后一个节点,并且是支持双向循环的。
void Link::insert(Node &n)
{
Node *tail = head;
if (head->next == head)
{
Node *newNode = new Node(n);
head->next = newNode;
head->prior = newNode;
newNode->prior = head;
newNode->next = head;
}
else
{
while (tail->next != head)
{
tail = tail->next;
}
Node *newNode = new Node(n);
newNode->next = head;
head->prior = newNode;
newNode->prior = tail;
tail->next = newNode;
}
}
然后我们讲遍历,两种遍历的方法基本一致,只是顺序不一样而已,我只讲一种顺序遍历。首先创建一根副指针p,p指向头节点的下一个节点,然后将其输出出来,然后使p=p->next,实现指向再下一个节点,以循环不断做,直到p指向的是头结点才结束,这样就能够全部打印一遍。
void Link::traverse1()
{
Node *p = head->next;
while (p != head)
{
p->display();
p = p->next;
}
cout << endl;
}
然后是删除,按关键字删除,那么首先要做的就是找到关键字所在地方,建造两根副指针,p,q;p指向头结点的下一个节点,q指向本节点,以p查看每个节点信息,q作为他后面的节点,方便q的修改,当找到关键字后,先修改q的next,使其变成p的next,然后删除p节点,这样p所在位置的节点就被删除了,然后还需要做的是将原来p的next的节点的prior指向q,所以我们让p指向该节点,然后使他的prior指向q.这样就完成删除。(查找过程依然采用遍历查找)
void Link::deleteByID(int id)
{
Node *p = head->next;
Node *q = head;
while (p != head)
{
if (p->ID == id)
{
q->next = p->next;
delete(p);
p = q->next;
p->prior = q;
}
else
{
p = p->next;
q = q->next;
}
}
}
最后讲的是按关键字修改。当然了,按关键字修改最重要的依然是找到关键字,所以重复同样的方法,创建副指针,循环查找,当找到关键字的时候,将信息修改成传入的信息即可。
void Link::modifyByID(int id, string name)
{
Node *p = head->next;
Node *q = head->prior;
while (p != head)
{
if (p->ID == id)
{
p->name = name;
}
p = p->next;
q = q->prior;
}
}
最后补充说一下,查找的过程中可能遍历了整个链都找不到关键字,此时应该要报错或者有其他处理方法,我这里写的都是认为一定可以找到的,所以没有进行处理,有需要的还是要做一下处理。
那么双循环链表就讲到这里,如果有不同意见或者见解,又或者觉得我有哪里写错了,欢迎私聊我,大家一起学习一起进步,谢谢啦(QQ:2245440426)
完整代码如下
#include<iostream>
#include<string>
using namespace std;
/*######################################*/
/* 学生类双链表 */
/*######################################*/
class Node
{
public:
int ID;
string name;
Node * next;
Node * prior;
Node() :next(nullptr), prior(nullptr) {}
Node(int ID, string name) :ID(ID), name(name), next(nullptr), prior(nullptr) {}
Node(Node &n) :ID(n.ID), name(n.name), next(nullptr), prior(nullptr) {}
void display()
{
cout << "ID:" << ID << endl << "Name:" << name << endl;
}
};
class Link
{
private:
Node * head;
public:
Link();
~Link();
void traverse1();
void traverse2();
void insert(Node &n);
void deleteByID(int id);
void modifyByID(int id, string name);
};
Link::Link()
{
head = new Node();
head->name = "头结点";
head->next = head;
head->prior = head;
}
Link::~Link()
{
while (head != nullptr)
{
Node *p = head->next;
delete(head);
head = p;
}
}
void Link::traverse1()
{
Node *p = head->next;
while (p != head)
{
p->display();
p = p->next;
}
cout << endl;
}
void Link::traverse2()
{
Node *p = head->prior;
while (p != head)
{
p->display();
p = p->prior;
}
cout << endl;
}
void Link::insert(Node &n)
{
Node *tail = head;
if (head->next == head)
{
Node *newNode = new Node(n);
head->next = newNode;
head->prior = newNode;
newNode->prior = head;
newNode->next = head;
}
else
{
while (tail->next != head)
{
tail = tail->next;
}
Node *newNode = new Node(n);
newNode->next = head;
head->prior = newNode;
newNode->prior = tail;
tail->next = newNode;
}
}
void Link::deleteByID(int id)
{
Node *p = head->next;
Node *q = head;
while (p != head)
{
if (p->ID == id)
{
q->next = p->next;
delete(p);
p = q->next;
p->prior = q;
}
else
{
p = p->next;
q = q->next;
}
}
}
void Link::modifyByID(int id, string name)
{
Node *p = head->next;
Node *q = head->prior;
while (p != head)
{
if (p->ID == id)
{
p->name = name;
}
p = p->next;
q = q->prior;
}
}
int main()
{
Link link;
Node s1(1001, "张三");
Node s2(1002, "李四");
Node s3(2001, "王五");
link.insert(s1);
link.insert(s2);
link.insert(s3);
link.traverse1();
link.traverse2();
link.deleteByID(s2.ID);
link.traverse1();
link.traverse2();
link.modifyByID(s3.ID, "赵六");
link.traverse1();
link.traverse2();
return 0;
}