目录
1 删除元素
1.1 删除值为 x 的所有结点
1.1.1 不带头结点的单链表
- 正常写法:
void Delete_X (LinkList &L, int x){
LinkNode *p = L;
LinkNode *prevNode; // 记录 p 的前驱结点
LinkNode *nextNode; // 记录 p 的后继结点
while (p != NULL){
if (p->data == x){
if (p == L){ // 如果是头结点
nextNode = p->next;
free(p);
L = nextNode;
}
else{ // 如果是中间结点或尾结点
nextNode = p->next;
free(p);
prevNode->next = nextNode;
}
}
prevNode = p; // 记录 p 的前驱结点
p = p->next; // 遍历下一个结点
}
}
- 递归写法:
void Delete_X (LinkList &L, int x){
LinkNode *p;
if (L->data == x){
p = L;
L = L->next;
free(p);
Delete_X(L, x);
}
else
Delete_X(L->next, x);
}
1.1.2 带头结点的单链表
void Delete_X (LinkList &L, int x){
LinkNode *p;
LinkNode *prevNode; // 记录 p 的前驱结点
LinkNode *nextNode; // 记录 p 的后继结点
p = L->next;
prevNode = L;
while (p != NULL){
if (p->data == x){
nextNode = p->next;
free(p);
prevNode->next = nextNode;
}
prevNode = p; // 记录 p 的前驱结点
p = p->next; // 遍历下一个结点
}
}
【注】欲删除值介于 s 和 t 之间的所有结点,需把条件改为
(s <= p->data) && (p->data <= t)
。
1.2 删除重复元素
1.2.1 不带头结点的单链有序表
void Delete_Same (LinkList &L){
LinkNode *p = L; // 初始指向链表头部
LinkNode *nextNode; // 记录 p 的后继结点
while (p != NULL){
nextNode = p->next; // 记录 p 的后继结点
if ((nextNode != NULL) && (nextNode->data == p->data)){ // 发现重复元素
p->next = nextNode->next;
free(nextNode);
}
else // 没有发现重复元素
p = p->next; // 遍历下一个结点
}
}
1.2.2 带头结点的单链有序表
void Delete_Same (LinkList &L){
LinkNode *p = L->next; // 初始指向链表第一个结点
LinkNode *nextNode; // 记录 p 的后继结点
while (p != NULL){
nextNode = p->next; // 记录 p 的后继结点
if ((nextNode != NULL) && (nextNode->data == p->data)){ // 发现重复元素
p->next = nextNode->next;
free(nextNode);
}
else // 没有发现重复元素
p = p->next; // 遍历下一个结点
}
}
2 链表逆置
2.1 逆序输出结点
2.1.1 不带头结点的单链表
设 L 为不带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
void Reverse_Print (LinkList &L){
LinkNode *p = L; // 指向链表头部
Stack S; // 定义栈
int x;
InitStack(S); // 初始化栈
while (p != NULL){
PushStack(S, p->data); // 入栈
p = p->next;
}
while (!EmptyStack(S)){
x = PopStack(S); // 出栈
输出 x;
}
}
2.1.2 带头结点的单链表
设 L 为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
void Reverse_Print (LinkList &L){
LinkNode *p = L->next;
Stack S; // 定义栈
int x;
InitStack(S); // 初始化栈
while (p != NULL){
PushStack(S, p->data); // 入栈
p = p->next;
}
while (!EmptyStack(S)){
x = PopStack(S); // 出栈
输出 x;
}
}
2.2 就地逆置
2.2.1 不带头结点的单链表
试编写一个算法将不带头结点的单链表就地逆置。
就地:空间复杂度为 O(1)。
思路:总是把第一个结点取下来,然后用尾插法重新建立链表。
LinkList Reverse (LinkList &L){
LinkNode *head = L; // 指向原链表头部
LinkNode *nextNode; // 保存原链表头部的指针域
LinkNode *newHead = NULL; // 指向新链表头部
while (head != NULL){
nextNode = head->next; // 保存原链表头部的指针域
head->next = newHead; // 原链表头部的指针域指向新链表头部
newHead = head; // 新链表头部更新
head = nextNode; // 原链表头部更新
}
L = newHead;
return L;
}
2.2.2 带头结点的单链表
LinkList Reverse (LinkList &L){
LinkNode *head = L->next; // 指向原链表第一个结点
LinkNode *nextNode; // 保存原链表头部的指针域
LinkNode *newHead = NULL; // 指向新链表头部
while (head != NULL){
nextNode = head->next; // 保存原链表头部的指针域
head->next = newHead; // 原链表头部的指针域指向新链表头部
newHead = head; // 新链表头指针更新
head = nextNode; // 原链表头指针更新
}
L->next = newHead; // 更新头结点指针域,头结点插到新链表头部
return L;
}
3 链表有序
3.1 链表排序
3.1.1 不带头结点的单链表
有一个不带头结点的单链表 L,设计一个算法使其元素递增有序。
思路:每次取出原链表的第一个元素,与有序链表中每个元素进行对比,然后插入到有序链表的相应位置。
void Sort (LinkList &L){
LinkNode *head = L; // 原链表头指针
LinkNode *nextNode; // 保存原链表头部的指针域
LinkNode *newHead; // 有序链表头指针
LinkNode *newNode, *newPrev; // 有序链表结点指针及其前驱结点
newHead = (LinkNode *) malloc(sizeof(LinkNode));
newHead->data = L->data;
newHead->next = NULL;
while (head != NULL){
newPrev = NULL;
newNode = newHead;
while ((newNode != NULL) && (head->data <= newNode->data)){ // 有序链表指针指向第一个大于 head 元素的结点
newPrev = newNode; // 记录其前驱结点
newNode = newNode->next; // 继续遍历新链表
} // 运行到此处,newPrev 在前,newNode 在后
nextNode = head->next; // 保存原链表头部的指针域
if (newPrev == NULL){ // 如果要插入有序链表的头部位置
head->next = newNode;
newHead = head; // 更新有序链表头指针
}
else{ // 如果要插入有序链表的中间和尾部位置
newPrev->next = head;
head->next = newNode;
}
head = nextNode; // 继续遍历原链表
}
L = newHead;
}
3.1.2 带头结点的单链表
有一个带头结点的单链表 L,设计一个算法使其元素递增有序。
void Sort (LinkList &L){
LinkNode *head = L->next; // 原链表头指针
LinkNode *nextNode; // 保存原链表头部的指针域
LinkNode *newNode, *newPrev; // 有序链表结点指针及其前驱结点
while (head != NULL){
newPrev = L; // 原链表头结点作为新链表的头结点
newNode = L->next;
while ((newNode != NULL) && (head->data <= newNode->data)){ // 有序链表指针指向第一个大于 head 元素的结点
newPrev = newNode; // 记录其前驱结点
newNode = newNode->next; // 继续遍历新链表
} // 运行到此处,newPrev 在前,newNode 在后
nextNode = head->next; // 保存原链表头部的指针域
newPrev->next = head;
head->next = newNode;
head = nextNode; // 继续遍历原链表
}
}
3.2 链表有序输出
3.2.1 不带头结点的单链表
按递增次序输出单链表各结点的数据元素,每输出一个将该结点删除。
思路:每次遍历链表找出最小元素,输出该元素,然后删除。
void Print_Min (LinkList &L){
LinkNode *prev; // 扫描指针
LinkNode *prevNode, *nodeMin = L, *nextNode; // 最小值结点(初始指向链表第一个结点)以及其前驱和后继结点
int min = L->data;
while (L != NULL){
prev = L;
while (prev->next != NULL){
if (prev->next->data < min){
prevNode = prev; // 记录最小值的前驱结点
nodeMin = prev->next; // 记录最小值结点
min = nodeMin->data;
nextNode = prev->next->next;// 记录最小值的后继结点
}
prev = prev->next;
}
输出 min;
if (nodeMin == L){ // 如果第一个结点为最小值
free(nodeMin);
L = nextNode;
}
else{
free(nodeMin);
prevNode->next = nextNode;
}
}
}
3.2.2 带头结点的单链表
按递增次序输出单链表各结点的数据元素,每输出一个将该结点删除。
思路:每次遍历链表找出最小元素,输出该元素,然后删除。
void Print_Min (LinkList &L){
LinkNode *prev; // 扫描指针
LinkNode *nodeMin = L->next, *prevNode, *nextNode; // 最小值结点(初始指向链表第一个结点)以及其前驱和后继结点
int min = L->next->data;
while (L->next != NULL){ // 循环到只剩头结点
prev = L;
while (prev->next != NULL){
if (prev->next->data < min){
prevNode = prev; // 记录最小值的前驱结点
nodeMin = prev->next; // 记录最小值结点
min = nodeMin->data;
nextNode = prev->next->next;// 记录最小值的后继结点
}
prev = prev->next;
}
输出 min;
free(nodeMin); // 删除最小值结点
prevNode->next = nextNode;
}
free(L);
}
4 合并链表
4.1 两个递增链表合并为一个递增链表
4.1.1 不带头结点的单链表
思路:设置双指针,建立新链表,运用尾插法,把两个链表的结点按升序插入即可。
LinkList Merge (LinkList &A, LinkList &B){
LinkNode *pa = A, *pb = B; // 扫描指针,指向前驱结点
LinkNode *prevNode, *node, *nextNode; // 记录前驱结点、当前要插入的结点、后继结点
LinkNode *C, *newNode; // 新链表
LinkNode *tail; // 新链表的尾指针
if (A->data < B->data){ // 单独处理两个链表的第一个结点,这两个结点单独保留,将问题转化为“带头结点的单链表”
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第一个结点
newNode->data = A->data;
C = newNode;
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第二个结点
newNode->data = B->data;
newNode->next = NULL;
C->next = newNode;
tail = newNode;
}
else{
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第一个结点
newNode->data = B->data;
C = newNode;
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第二个结点
newNode->data = A->data;
newNode->next = NULL;
C->next = newNode;
tail = newNode;
}
while ((pa->next != NULL) && (pb->next != NULL)){
if (pb->next->data < pa->next->data){
prevNode = pb; // 记录前驱结点···(1)
node = pb->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
tail->next = node; // 往新链表尾插当前结点···(3)
tail = node; // 更新新链表尾指针
pb = pb->next; // 链表 B 的扫描指针继续遍历···(4)
}
else{
prevNode = pa; // 记录前驱结点···(1)
node = pa->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
tail->next = node; // 往新链表尾插当前结点···(3)
tail = node; // 更新新链表尾指针
pa = pa->next; // 链表 A 的扫描指针继续遍历···(4)
}
}
// 运行到此处,一定有且仅有一个链表非空
if (A->next != NULL) // 若链表 A 还未空
tail->next = A->next; // 新链表尾部直接插入链表 A 的剩余部分
if (B->next != NULL) // 若链表 B 还未空
tail->next = B->next; // 新链表尾部直接插入链表 B 的剩余部分
free(A); // 删除链表 A
free(B); // 删除链表 B
return C;
}
4.1.2 带头结点的单链表
思路:设置双指针,建立新链表,运用尾插法,把两个链表的结点按升序插入即可。
LinkList Merge (LinkList &A, LinkList &B){
LinkNode *pa = A, *pb = B; // 扫描指针,指向前驱结点
LinkNode *prevNode, *node, *nextNode; // 记录前驱结点、当前要插入的结点、后继结点
LinkNode *C = (LinkNode *) malloc(sizeof(LinkNode)); // 新链表的头结点
LinkNode *tail = C; // 新链表的尾指针
while ((pa->next != NULL) && (pb->next != NULL)){
if (pb->next->data < pa->next->data){
prevNode = pb; // 记录前驱结点···(1)
node = pb->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
tail->next = node; // 往新链表尾插当前结点···(3)
tail = node; // 更新新链表尾指针
pb = pb->next; // 链表 B 的扫描指针继续遍历···(4)
}
else{
prevNode = pa; // 记录前驱结点···(1)
node = pa->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
tail->next = node; // 往新链表尾插当前结点···(3)
tail = node; // 更新新链表尾指针
pa = pa->next; // 链表 A 的扫描指针继续遍历···(4)
}
}
// 运行到此处,一定有且仅有一个链表非空
if (A->next != NULL) // 若链表 A 还未空
tail->next = A->next; // 新链表尾部直接插入链表 A 的剩余部分
if (B->next != NULL) // 若链表 B 还未空
tail->next = B->next; // 新链表尾部直接插入链表 B 的剩余部分
free(A); // 删除链表 A 的头结点
free(B); // 删除链表 B 的头结点
return C;
}
4.2 两个递增链表合并为一个递减链表
4.2.1 不带头结点的单链表
思路:设置双指针,建立新链表,运用头插法,把两个链表的结点按升序插入即可。
LinkList Merge (LinkList &A, LinkList &B){
LinkNode *pa = A, *pb = B; // 扫描指针,指向前驱结点
LinkNode *prevNode, *node, *nextNode; // 记录前驱结点、当前要插入的结点、后继结点
LinkNode *C, *newNode; // 新链表
LinkNode *headNext; // 新链表的头结点后继指针
if (A->data > B->data){ // 单独处理两个链表的第一个结点,这两个结点单独保留,将问题转化为“带头结点的单链表”
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第一个结点
newNode->data = A->data;
C = newNode;
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第二个结点
newNode->data = B->data;
newNode->next = NULL;
C->next = newNode;
}
else{
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第一个结点
newNode->data = B->data;
C = newNode;
newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第二个结点
newNode->data = A->data;
newNode->next = NULL;
C->next = newNode;
}
while ((pa->next != NULL) && (pb->next != NULL)){
if (pb->next->data < pa->next->data){
prevNode = pb; // 记录前驱结点···(1)
node = pb->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
node->next = C; // 当前结点插入到新链表中···(3)
C = node; // 更新新链表的头指针
pb = pb->next; // 链表 B 的扫描指针继续遍历···(4)
}
else{
prevNode = pa; // 记录前驱结点···(1)
node = pa->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
node->next = C; // 当前结点插入到新链表中···(3)
C = node; // 更新新链表的头指针
pa = pa->next; // 链表 A 的扫描指针继续遍历···(4)
}
}
// 运行到此处,一定有且仅有一个链表非空
if (A->next != NULL){ // 若链表 A 还未空
while (pa->next != NULL)// 先找到链表 A 的尾部
pa = pa->next;
headNext = head->next; // 记录新链表头结点的后继指针
head->next = A->next; // 新链表尾部直接插入链表 A 的剩余部分
pa->next = headNext;
}
if (B->next != NULL){ // 若链表 B 还未空
while (pb->next != NULL)// 先找到链表 B 的尾部
pb = pb->next;
headNext = head->next; // 记录新链表头结点的后继指针
tail->next = B->next; // 新链表尾部直接插入链表 B 的剩余部分
pb->next = headNext;
}
free(A); // 删除链表 A
free(B); // 删除链表 B
return C;
}
4.2.2 带头结点的单链表
思路:设置双指针,建立新链表,运用头插法,把两个链表的结点按升序插入即可。
LinkList Merge (LinkList &A, LinkList &B){
LinkNode *pa = A, *pb = B; // 扫描指针,指向前驱结点
LinkNode *prevNode, *node, *nextNode; // 记录前驱结点、当前要插入的结点、后继结点
LinkNode *C = (LinkNode *) malloc(sizeof(LinkNode)); // 新链表的头结点
LinkNode *head = C, *headNext = C->next; // 新链表的头结点,及头结点的后继指针
while ((pa->next != NULL) && (pb->next != NULL)){
if (pb->next->data < pa->next->data){
prevNode = pb; // 记录前驱结点···(1)
node = pb->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
headNext = head->next; // 记录新链表头结点的后继指针···(3)
head->next = node; // 往新链表尾插当前结点
node->next = headNext; // 当前结点指向头结点的后继指针
pb = pb->next; // 链表 B 的扫描指针继续遍历···(4)
}
else{
prevNode = pa; // 记录前驱结点···(1)
node = pa->next; // 记录当前要插入的结点
nextNode = node->next; // 记录后继结点
prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
headNext = head->next; // 记录新链表头结点的后继指针···(3)
head->next = node; // 往新链表尾插当前结点
node->next = headNext; // 当前结点指向头结点的后继指针
pa = pa->next; // 链表 A 的扫描指针继续遍历···(4)
}
}
// 运行到此处,一定有且仅有一个链表非空
if (A->next != NULL){ // 若链表 A 还未空
while (pa->next != NULL)// 先找到链表 A 的尾部
pa = pa->next;
headNext = head->next; // 记录新链表头结点的后继指针
head->next = A->next; // 新链表尾部直接插入链表 A 的剩余部分
pa->next = headNext;
}
if (B->next != NULL){ // 若链表 B 还未空
while (pb->next != NULL)// 先找到链表 B 的尾部
pb = pb->next;
headNext = head->next; // 记录新链表头结点的后继指针
tail->next = B->next; // 新链表尾部直接插入链表 B 的剩余部分
pb->next = headNext;
}
free(A); // 删除链表 A 的头结点
free(B); // 删除链表 B 的头结点
return C;
}
5 拆分链表
5.1 不带头结点的单链表
设 C = {a1, b1, a2, b2, a3, b3,···}为线性表,采用不带头结点的单链表存放,设计一个就地算法,将其拆分为两个线性表,使得 A = {a1, a2, ···, an}, B = {bn,···,b2, b1}。
思路:链表 A 使用尾插法,链表 B 使用头插法。
void Split (LinkList &C){
LinkNode *headC, *headNextC, *headNextNextC; // headC 指向链表 C 的第二个元素,将其当做头结点
LinkNode *tailA, *headB; // 链表 A 的尾指针,链表 B 的头指针
LinkNode *A, *B;
bool flag = true;
// 首先单独处理链表 C 的第一个和第二个元素,将问题转化为“带头结点的单链表”
A = (LinkNode *) malloc(sizeof(LinkNode));
B = (LinkNode *) malloc(sizeof(LinkNode));
A->data = C->data;
A->next = NULL;
B->data = C->next->data;
B->next = NULL;
tailA = A;
headB = B;
headC = C->next->next; // headC 指向第二个元素,将其当做头结点
while (headC->next != NULL){
headNextC = headC->next; // 链表 C 的第三个元素
headNextNextC = headNextC->next; // 链表 C 的第四个元素
headC->next = headNextNextC; // 将链表 C 的第三个元素脱离出来
if (flag == true){ // 轮到 an
tailA->next = headNextC; // 尾插入到链表 A
tailA = headNextC;
flag = false;
}
else{ // 轮到 bn
headNextC->next = headB; // 头插入到链表 B
headB = headNextC;
flag = true;
}
}
free(C->next);
free(C);
}
5.2 带头结点的单链表
设 C= {a1, b1, a2, b2, a3, b3,···} 为线性表,采用带头结点的单链表存放,设计一个就地算法,将其拆分为两个线性表,使得 A = {a1, a2, ···, an}, B = {bn,···,b2, b1}。
思路:链表 A 使用尾插法,链表 B 使用头插法。
void Split (LinkList &C){
LinkNode *headC, *headNextC; // 链表 C 的第一个元素及其后继结点,链表 C 的第一个元素也是待插入元素
LinkNode *tailA, *headB, *headNextB; // 链表 A 的尾指针,链表 B 的第一个元素及其后继结点
LinkNode *A, *B;
bool flag = true;
A = (LinkNode *) malloc(sizeof(LinkNode));
B = (LinkNode *) malloc(sizeof(LinkNode));
A->next = NULL;
B->next = NULL;
tailA = A;
headB = B;
while (C->next != NULL){
headC = C->next; // 链表 C 的第一个元素
headNextC = headC->next; // 链表 C 的第二个元素
C->next = headNextC; // 将链表 C 的第一个元素脱离出来
if (flag == true){ // 轮到 an
tailA->next = headC; // 尾插入到链表 A
tailA = headC;
flag = false;
}
else{ // 轮到 bn
headB = B->next; // 链表 B 的第一个元素
headNextB = headB->next; // 链表 B 的第二个元素
B->next = headC; // 头插入到链表 B
headC->next = headNextB;
flag = true;
}
}
free(C);
}
6 链表对称和链表环
6.1 判断链表对称
设计一个算法用于判断带头结点的循环双链表是否对称。
bool Test (LinkList &L){
LinkNode *p, *q;
p = L->next;
q = L->prev;
// p != q 为奇数个结点的情况,检测结束时两指针会出现相遇
// q != p->next 为偶数个结点的情况,检测结束时两指针相邻
while ((p != q) && (q != p->next)){
if (p->data != q->data)
return false;
else{
p = p->next;
q = q->prev;
}
}
return true;
}
6.2 判断链表是否有环
单链表有环,是指单链表中某个节点的 next 指针域指向的是链表中在它之前的某一个节点,这样在链表的尾部形成一个环形结构。
条件:带头结点的单链表。
- 判断是否有环:通常使用快慢指针的方法,也就是说一个每次指针走两步,一个指针每次只走一步,如果链表有环,则两个指针总会在一个节点上相遇,若无环,则不会相遇。
bool Ring (LinkNode &L){
LinkNode *slow, *fast;
slow = L->next;
fast = L->next;
while ((fast != NULL) && (fast->next != NULL)){
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
return true;
}
return false;
}
- 返回环的开始结点:已知单链表有环的情况下,找到环开始的位置。当快慢指针相遇时,让其中一个指针指向头结点,然后让两个节点以相同的速度前进,再次相遇时所在的节点位置就是环开始的位置。
LNode *Ring (LinkNode &L){
LinkNode *slow, *fast;
slow = L->next;
fast = L->next;
while ((fast != NULL) && (fast->next != NULL) && (slow != fast)){
slow = slow->next;
fast = fast->next->next;
}
if ((slow == NULL) || (fast->next == NULL))
return NULL;
fast = L->next;
while (fast != slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
标签:结点,NULL,nextNode,next,链表,算法,数据结构,LinkNode
From: https://www.cnblogs.com/Mount256/p/16866467.html