单链表
单链表是数据节点是单向排列。包括两个域:数据域和指针域。单链表的节点数据结构如下:
// 单链表节点数据结构
class listNode
{
public:
int value; // 数据域
listNode *next; // 指针域
};
通常用“头指针”来标识一个单链表,头指针是指向单链表第一个节点的指针,如图1所示表head(head为头指针)。nullptr==head为空链表。
此外,为了方便操作,可以在单链表第一个节点之前附加一个节点,称为头节点,如图2所示。头节点的指针域指向链表的第一个节点,数据域可以不设任何信息。带头结点的单链表headNode为空的判定条件为nullptr==headNode。
单链表的建立
单链表的建立可以用头插法和尾插法。头插法是指建立单链表时,总是将新节点插入到当前链表的表头,通常用在将一个已存在的链表逆序。尾插法创建单链表代码如下:链表长度为6,节点值分别为1,2,3,4,5,6。
// 创建一个单链表(尾插法)
listNode* createList()
{
listNode *head = new listNode;
listNode *headNode = head;
if (headNode != nullptr)
{
for (int i = 1; i < 7; i++)
{
listNode *newNode = new listNode;
newNode->value = i;
newNode->next = nullptr;
headNode->next = newNode;
headNode = newNode;
headNode->next = nullptr;
}
return head->next;
}
return nullptr;
}
// 遍历单链表
void printList(listNode *head)
{
listNode *p = head;
while (p != nullptr)
{
cout << p->value << " ";
p = p->next;
}
cout << endl;
}
int main()
{
listNode *head = createList();
printList(head);
// 输出:1 2 3 4 5 6
return 0;
}
单链表的插入
向单链表的尾部插入一个值为val的节点:
(1) 链表是否为空;
(2) 链表非空时找到尾节点,插入值为val的节点;
(3) 当向空链表中插入新的节点会改动头指针,需要使用引用形参或者二维指针形参,否则除了这个函数仍然是一个空指针。
// 向单链表的尾部添加一个值为val的节点
void addtoTail(listNode *&head,int val) // head形参为引用类型
{
listNode *newNode = new listNode;
newNode->value = val;
newNode->next = nullptr;
listNode *p = head;
if (nullptr == head) // 链表为空时
{
head = newNode;
}
else // 链表非空时
{
listNode *p = head;
while (p->next != nullptr) // 找到尾节点的前驱节点
{
p = p->next;
}
p->next = newNode; // 插入新节点
}
}
int main()
{
listNode *head = createList();
addtoTail(head, 7);
printList(head);
// 输出:1,2,3,4,5,6,7
return 0;
}
单链表的删除
从单链表中删除一个值为val的节点:
(1) 链表是否为空;
(2) 删除的是首节点;
(3) 删除的是非首节点;
(4) 当所删除的节点是首节点时,会改动头指针,需要使用引用形参或者二维指针形参,否则会输出垃圾值,并显示编译错误。
// 从单链表中删除一个值为val的节点
void deleteNode(listNode *&head, int val) // head形参为引用类型
{
if (nullptr == head) // 链表为空
return;
listNode *deleteNode = nullptr; // 记录要删除的节点
if (head->value == val) // 删除的是首节点
{
deleteNode = head;
head = head->next;
}
else // 删除的是非首节点
{
listNode *p = head;
while (p->next != nullptr && p->next->value != val)
{
p = p->next; // 找到要删除的节点的前驱节点
}
if (p->next != nullptr && p->next->value == val)
{
deleteNode = p->next; // 记录
p->next = p->next->next;// 删除值为val的节点
}
}
if (deleteNode != nullptr)
{
delete deleteNode;
deleteNode = nullptr;
}
}
创建一个带环的单链表
创建一个带环的单链表,链表长度为6,环入口节点的值为3,代码如下:
// 创建一个带环的单链表,链表长度为6,环入口节点值为3
listNode* createListwithLoop()
{
listNode *head = new listNode;
listNode *headNode = head;
listNode *pSave = nullptr; // 定义一个节点指针,用于创建环
if (headNode != nullptr)
{
for (int i = 1; i < 7; i++)
{
listNode *newNode = new listNode; // 创建新节点
newNode->value = i; // 初始化数据域
newNode->next = nullptr; // 初始化数据域
headNode->next = newNode;// 头节点与新节点相链接,即:将新节点作为头节点的后继
headNode = newNode; // 头节点前进一步,作为即将创建的新节点的前驱,即为尾插法,不断从尾部插入新节点
headNode->next = nullptr;// 清空指针域,为链节下一个新节点做准备
if (i == 3)
{
pSave = newNode; // 保存第三个节点的指针,为在第三个节点处创建环做准备
}
}
headNode->next = pSave; // 将尾指针的next指向第三个节点,即:创建了一个有环的链表,环入口在第三个节点处
return head->next;
}
return nullptr;
}
判断单链表是否有环
快慢指针中的快慢是指移动的步长,及每次向前移动的快慢。如:可以让快指针每次沿链表向前移动2,慢指针每次向前移动1。
思路:使用快慢指针,让快慢指针均从链表头开始遍历,快指针每次向前移动两个位置,慢指针每次向前移动一个位置;如果快指针到达nullptr,说明链表以nullptr结尾,没有环;如果快指针追上慢指针,则表示单链表有环。设链表有环时快慢指针的相遇点为entryNode。码如下:
// 单链表的环
// 使用快慢指针:快指针走两步,慢指针走一步,快慢指针相遇则有环
// 找快慢指针相遇点,有则返回,无则返回nullptr;
listNode* getMeetNode(listNode *head)
{
listNode *p1 = head;
listNode *p2 = head;
while (p2 != nullptr && p2->next != nullptr)
{
p1 = p1->next;
p2 = p2->next->next;
if (p1 == p2)
return p2;
}
return nullptr;
}
// 判断链表是否有环
void hasLoop(listNode *head)
{
if (getMeetNode(head))
cout << "链表有环" << endl;
else
cout << "链表无环" << endl;
}
int main()
{
listNode *head = createListwithLoop();
hasLoop(head);
// 输出:链表有环
return 0;
}
求单链表中环的长度
思路:找到快慢指针的相遇点entryNode,entryNode沿着链表的环走一周(每次一步),再次回到相遇点,走的步数即为环的长度。代码如下:
// 环的长度
int getLoopLen(listNode *head)
{
if (nullptr == head)
return 0;
listNode *meetNode = getMeetNode(head);
listNode *p = meetNode;
int cnt = 1;
while (p->next != meetNode)
{
cnt++;
p = p->next;
}
return cnt;
}
int main()
{
listNode *head = createListwithLoop();
cout << "环的长度为" << getLoopLen(head) << endl;
// 输出:环的长度为4
return 0;
}
找单链表中环的入口节点
思路:环入口点到起始点的距离a和相遇点到环入口点的距离(r-X)相差环长度r整数倍。
假设链表存在环,链表长度为L,起始点到换入口的距离为a,环长度为r,则L=a+r;如图3所示。在快指针进入环到慢指针进入环前的这段时间,如环的长度较短,也许快指针已经在环中走了n圈,然后慢指针再进入环。设慢指针和快指在环中相遇时,慢指针在环中走了X步,则快、慢指针走的总步数K和M分别为K=a+nr+X,M=a+X。由K=2M得:a+nr+X=2(a+X),即:a=(n-1)r+r-X。
上式含义为:环入口点到起始点的距离a和相遇点到环入口点的距离(r-X)相差环长度r整数倍。
所以让慢指针回到起始点,快指针从相遇点起,两者同时出发,且步长均为1,则当两者相遇时,即为环入口节点。代码如下:
// 找环的入口节点
listNode *getEntryNode(listNode *head)
{
listNode *p1 = getMeetNode(head);
if (nullptr == p1)
return nullptr;
listNode *p2 = head;
while (p1 != p2)
{
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
int main()
{
listNode *head = createListwithLoop();
listNode *entryNode = getEntryNode(head);
cout << "环的入口节点值为" << entryNode->value << endl;
// 输出:环的入口节点值为3
return 0;
}
求单链表的长度
由上述可知,链表长度L=a+r,环的长度已求得,只需求起始点到环入口的距离a。让慢指针从起始点出发,到达环入口时,走的步长即为a。代码如下:
// 单链表的长度
int getListLen(listNode *head)
{
if (nullptr == head)
return 0;
listNode *entryNode = getEntryNode(head);
listNode *p = head;
int cnt = 0;
while (p != entryNode)
{
cnt++;
p = p->next;
}
int len = cnt + getLoopLen(head);
return len;
}
int main()
{
listNode *head = createListwithLoop();
cout << "单链表的长度为" << getListLen(head) << endl;
// 输出:单链表的长度为6
return 0;
}
上述代码汇总
#include<iostream>
using namespace std;
// 单链表节点数据结构
class listNode
{
public:
int value;
listNode *next;
};
// 遍历单链表
void printList(listNode *head)
{
listNode *p = head;
while (p != nullptr)
{
cout << p->value << " ";
p = p->next;
}
cout << endl;
}
// 创建一个单链表(尾插法)
listNode* createList()
{
listNode *head = new listNode;
listNode *headNode = head;
if (headNode != nullptr)
{
for (int i = 1; i < 7; i++)
{
listNode *newNode = new listNode;
newNode->value = i;
newNode->next = nullptr;
headNode->next = newNode;
headNode = newNode;
headNode->next = nullptr;
}
return head->next;
}
return nullptr;
}
// 向单链表的尾部添加一个值为val的节点
void addtoTail(listNode *&head,int val)
{
listNode *newNode = new listNode;
newNode->value = val;
newNode->next = nullptr;
listNode *p = head;
if (nullptr == head)
{
head = newNode;
}
else
{
listNode *p = head;
while (p->next != nullptr)
{
p = p->next;
}
p->next = newNode;
}
}
// 从单链表中删除一个值为val的节点
void deleteNode(listNode *&head,int val)
{
if (nullptr == head)
return;
listNode *deleteNode = nullptr;
if (head->value == val)
{
deleteNode = head;
head = head->next;
}
else
{
listNode *p = head;
while (p->next != nullptr && p->next->value != val)
{
p = p->next;
}
if (p->next != nullptr && p->next->value == val)
{
deleteNode = p->next;
p->next = p->next->next;
}
}
if (deleteNode != nullptr)
{
delete deleteNode;
deleteNode = nullptr;
}
}
// 创建一个带环的单链表,链表长度为6,环入口节点值为3
listNode* createListwithLoop()
{
listNode *head = new listNode;
listNode *headNode = head;
listNode *pSave = nullptr; // 定义一个节点指针,用于创建环
if (headNode != nullptr)
{
for (int i = 1; i < 7; i++)
{
listNode *newNode = new listNode; // 创建新节点
newNode->value = i; // 初始化数据域
newNode->next = nullptr; // 初始化数据域
headNode->next = newNode;// 头节点与新节点相链接,即:将新节点作为头节点的后继
headNode = newNode; // 头节点前进一步,作为即将创建的新节点的前驱,即为尾插法,不断从尾部插入新节点
headNode->next = nullptr;// 清空指针域,为链节下一个新节点做准备
if (i == 3)
{
pSave = newNode; // 保存第三个节点的指针,为在第三个节点处创建环做准备
}
}
headNode->next = pSave; // 将尾指针的next指向第三个节点,即:创建了一个有环的链表,环入口在第三个节点处
return head->next;
}
return nullptr;
}
// 单链表的环
// 使用快慢指针:快指针走两步,慢指针走一步,快慢指针相遇则有环
// 找快慢指针相遇点,有则返回,无则返回nullptr;
listNode* getMeetNode(listNode *head)
{
listNode *p1 = head;
listNode *p2 = head;
while (p2 != nullptr && p2->next != nullptr)
{
p1 = p1->next;
p2 = p2->next->next;
if (p1 == p2)
return p2;
}
return nullptr;
}
// 判断链表是否有环
void hasLoop(listNode *head)
{
if (getMeetNode(head))
cout << "链表有环" << endl;
else
cout << "链表无环" << endl;
}
// 找环的入口节点
listNode *getEntryNode(listNode *head)
{
listNode *p1 = getMeetNode(head);
if (nullptr == p1)
return nullptr;
listNode *p2 = head;
while (p1 != p2)
{
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
// 环的长度
int getLoopLen(listNode *head)
{
if (nullptr == head)
return 0;
listNode *meetNode = getMeetNode(head);
listNode *p = meetNode;
int cnt = 1;
while (p->next != meetNode)
{
cnt++;
p = p->next;
}
return cnt;
}
// 单链表的长度
int getListLen(listNode *head)
{
if (nullptr == head)
return 0;
listNode *entryNode = getEntryNode(head);
listNode *p = head;
int cnt = 0;
while (p != entryNode)
{
cnt++;
p = p->next;
}
int len = cnt + getLoopLen(head);
return len;
}
int main()
{
cout << "无环单链表的遍历、插入、删除" << endl;
cout << "----------------------------------------------" << endl;
listNode *head = createList();
printList(head);
addtoTail(head, 7);
printList(head);
deleteNode(head, 1);
printList(head);
cout << "----------------------------------------------" << endl;
cout << "带环单链表的判断、入口节点、环长度、链表长度" << endl;
listNode *headNode = createListwithLoop();
hasLoop(headNode);
listNode *entryNode = getEntryNode(headNode);
cout << "环的入口节点值为" << entryNode->value << endl;
cout << "环的长度为" << getLoopLen(headNode) << endl;
cout << "单链表的长度为" << getListLen(headNode) << endl;
cout << "----------------------------------------------" << endl;
return 0;
}
运行结果: