当前位置: 首页 > news >正文

数据结构与算法(2)-线性表的应用

1、使用双指针找到第K个节点

具体思路:

双指针法:

先假设一个快指针一个慢指针,都指在第一个节点

假设要找倒数第三个节点,那先让快指针走三步,走完之后再让快指针和慢指针同步去走,直到快指针指向了链表末尾的NULL,此时慢指针指到了目标

核心思路:

int findNodeFS(Node *L,int k){Node *fast=L->next;Node *slow=L->next;for (int i = 0; i < k; i++){fast=fast->next;}while (fast!=NULL){fast=fast->next;slow=slow->next;}printf("倒数第%d个节点值为:%d\n",k,slow->data);
}

解题:

一,算法基本思想(思路)

在不改变链表结构的前提下,要找到倒数第k个节点,可以用两个指针法

  1. 定义两个指针 fastslow,都指向首元节点(不是头结点)。
  2. 先让 fast 向前移动 k 步,这样 fastslow 之间的距离就为 k
  3. 然后两个指针同时往后移动,当 fast 到达链表末尾时,slow 所指的位置就是倒数第 k 个节点
  4. 如果 fast 提前到达 NULL,说明链表长度小于 k,返回 0。

该算法只需遍历一遍链表,时间复杂度 O(n),空间复杂度 O(1)。


二,算法详细实现步骤

  1. 用两个指针 fastslow 指向首元节点;
  2. fast 向前移动 k 步,如果此时 fast == NULL,说明 k 大于链表长度,返回 0;
  3. 否则同时移动 fastslow,直到 fast == NULL
  4. 此时 slow 即为倒数第 k 个节点;
  5. 输出其 data 域值并返回 1。

三、C语言完整实现(含注释)

#include <stdio.h>
#include <stdlib.h>typedef int ElemType;
typedef struct node {ElemType data;struct node *next;
} Node;// 查找倒数第k个节点
int findNodeFS(Node *L, int k) {Node *fast = L->next;  // 首元结点Node *slow = L->next;int i;// fast先走k步for (i = 0; i < k; i++) {if (fast == NULL) {return 0; // 链表长度小于k}fast = fast->next;}// fast和slow同时走,直到fast到达末尾while (fast != NULL) {fast = fast->next;slow = slow->next;}printf("倒数第%d个节点的值为: %d\n", k, slow->data);return 1;
}// 辅助函数:创建链表
Node* createList(int n) {Node *head = (Node*)malloc(sizeof(Node));head->next = NULL;Node *tail = head;for (int i = 1; i <= n; i++) {Node *p = (Node*)malloc(sizeof(Node));p->data = i;p->next = NULL;tail->next = p;tail = p;}return head;
}int main() {Node *L = createList(5); // 创建一个 1→2→3→4→5 的链表int k = 2;if (!findNodeFS(L, k))printf("查找失败:链表长度小于 %d\n", k);return 0;
}输出:
倒数第2个节点的值为: 4

四、总结

方法时间复杂度空间复杂度思想
双指针法O(n)O(1)一次遍历即可找到倒数第k个节点

2、找相同后缀

核心思路:

  1. 分别求出两个链表的长度m和n

  1. fast指针指向较长的链表,其先走m-n或者n-m步

  1. 同步移动指针,判断他们是否指向同一节点.当指向同一节点时,找到了

核心思路:

typedef struct Node {char data;struct Node *next;
} Node;/* 计算带头结点单链表的长度(不含头结点) */
int length(Node *head) {int len = 0;for (Node *p = head->next; p != NULL; p = p->next) len++;return len;
}/* 从当前结点向前走 k 步(p 是首元结点而不是头结点) */
Node* advance(Node *p, int k) {while (k-- > 0 && p) p = p->next;return p;
}/* 返回两个带头结点单链表“共享后缀”的起始结点;若无则返回 NULL */
Node* find_common_tail(Node *str1, Node *str2) {Node *p1 = str1->next;   // 首元结点Node *p2 = str2->next;int m = length(str1);int n = length(str2);// 让较长链表的指针先走 |m-n| 步,对齐剩余长度if (m > n) p1 = advance(p1, m - n);else       p2 = advance(p2, n - m);// 同步前进,第一次指针相等处即为共享后缀起点while (p1 && p2 && p1 != p2) {p1 = p1->next;p2 = p2->next;}return (p1 == p2) ? p1 : NULL;
}

做题目:

  1. 基本设计思路
  • 设两条带头结点的单链表分别为 str1str2,首元结点为 str1->nextstr2->next
  • 先分别求出两表长度 m、n(不含头结点)。
  • 让较长表的指针先前进 |m-n| 步,使两指针到尾部的剩余长度相同(对齐)。
  • 然后两指针同步前进,第一次出现 指针地址相等 的结点即为“共享后缀”的起点;若直到 NULL 都未相等,则不存在共享后缀。

关键点:判断“同一结点”必须比较指针地址p1 == p2),不能比较 data


  1. 代码实现(C,含必要注释)
#include <stdio.h>
#include <stdlib.h>typedef struct Node {char data;struct Node *next;
} Node;// 初始化链表(带头结点)
Node* initList() {Node *head = (Node*)malloc(sizeof(Node));head->data = 0;     // 头结点数据无效head->next = NULL;return head;
}// 创建新节点
Node* newNode(char ch) {Node *p = (Node*)malloc(sizeof(Node));p->data = ch;p->next = NULL;return p;
}// 计算链表长度(不含头结点)
int length(Node *L) {int len = 0;Node *p = L->next;while (p) {len++;p = p->next;}return len;
}// 从当前结点前进k步
Node* advance(Node *p, int k) {while (k-- > 0 && p) p = p->next;return p;
}// 查找两个链表的公共后缀起点
Node* findCommon(Node *L1, Node *L2) {Node *p1 = L1->next;Node *p2 = L2->next;int len1 = length(L1);int len2 = length(L2);// 对齐:让长的先走 |len1 - len2| 步if (len1 > len2) p1 = advance(p1, len1 - len2);else              p2 = advance(p2, len2 - len1);// 同步前进,直到相遇while (p1 && p2 && p1 != p2) {p1 = p1->next;p2 = p2->next;}return (p1 == p2) ? p1 : NULL;
}// 打印链表
void printList(Node *L) {Node *p = L->next;while (p) {printf("%c ", p->data);p = p->next;}printf("\n");
}int main() {// 创建两个带头结点的链表Node *str1 = initList();Node *str2 = initList();// 创建共享后缀 "i"->"n"->"g"Node *i = newNode('i');Node *n = newNode('n');Node *g = newNode('g');i->next = n;n->next = g;// 构造 str1: l->o->a->d->i->n->gNode *l = newNode('l');Node *o = newNode('o');Node *a = newNode('a');Node *d = newNode('d');str1->next = l;l->next = o;o->next = a;a->next = d;d->next = i;// 构造 str2: b->e->i->n->gNode *b = newNode('b');Node *e = newNode('e');str2->next = b;b->next = e;e->next = i;// 打印链表printf("str1: ");printList(str1);printf("str2: ");printList(str2);// 查找公共后缀起点Node *p = findCommon(str1, str2);if (p)printf("公共后缀起点字符为: %c\n", p->data);elseprintf("无公共后缀\n");return 0;
}

  1. 时间复杂度说明
  • 计算两个长度:各遍历一次,代价 O(m) + O(n)
  • 对齐时最多前进 |m-n| 步;
  • 同步扫描最多再走 min(m, n) 步;
  • 整体仍是一次线性遍历数量级,**时间复杂度 **O(m+n);只用常数个指针变量,**空间复杂度 **O(1)

3、去除相同元素(绝对值相同)

思路:

题目第一句话的意思是:

节点数是固定的 n 个

每个节点的数据值范围是 [-n, n] 之间

比如说 n = 5,那这个链表中就有 5 个节点,每个节点的 data 满足:

|data|5
即 data ∈ {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}

拿空间换时间,遍历一次即可

做一个数组,下标从0到21,因为链表中的值的绝对值最大是21,我们从链表第一个结点开始,链表第一个值是21,那么数组下标为21处的值设为1,链表第二个数的绝对值是15,那么数组下标为15处的绝对值设成15,链表第三个数的绝对值是15,此时去数组发现下标为15的地方已经标值1了,所以这个是重复的,把链表第三个节点删除.循此往后直到链表末尾即可

核心思路:

void removeNode(Node *L, int n)
{Node *p = L;                 // p 指向“待检查结点”的前驱(从头结点开始)int index;int *q = (int*)malloc(sizeof(int) * (n + 1)); // 标记数组 seen[0..n]// 初始化标记数组为 0for (int i = 0; i < n + 1; i++) *(q + i) = 0;while (p->next != NULL) {// 取被检查结点的绝对值index = abs(p->next->data);if (*(q + index) == 0) {      // 第一次出现:做标记,后移 p*(q + index) = 1;p = p->next;} else {                      // 重复出现:删除 p->nextNode *temp = p->next;p->next = temp->next;free(temp);}}free(q);
}
  1. 基本设计思路
  • 题设给出:链表存 n 个整数,且对任一结点 |data| ≤ n
  • 开一个标记数组 seen[0..n],初值全 0。
  • 设指针 p头结点开始,始终保持 p 是“待检查结点的前驱”。
    • index = abs(p->next->data)
    • seen[index] == 0:说明该绝对值第一次出现,置 seen[index]=1p = p->next
    • 否则:该绝对值重复,删除 p->nextp->next = p->next->next; free(被删结点)),p 不动
  • 一趟扫描完成。
  1. 结点数据类型定义
typedef struct Node {int data;            // 结点的整数值,可能为负,且 |data| ≤ nstruct Node *next;   // 指向后继
} Node;
  1. C 代码
#include <stdio.h>
#include <stdlib.h>   // malloc, free
#include <math.h>     // abstypedef struct Node {int data;struct Node *next;
} Node;/* 带头结点初始化 */
Node* initList(void){Node *head = (Node*)malloc(sizeof(Node));head->data = 0;head->next = NULL;return head;
}/* 新结点 */
Node* newNode(int x){Node *p = (Node*)malloc(sizeof(Node));p->data = x;p->next = NULL;return p;
}/* 尾插(演示用) */
void push_back(Node *L, int x){Node *p = L;while (p->next) p = p->next;p->next = newNode(x);
}/* 打印(不含头结点) */
void printList(Node *L){for (Node *p = L->next; p; p = p->next) printf("%d ", p->data);printf("\n");
}/* ---------- 关键:删除绝对值相同(仅保留首次出现) ---------- */
/* 参数 n 是 |data| 的上界(题设给定或可由输入得知) */
void removeNode(Node *L, int n){Node *p = L;                                // p 为前驱,从头结点开始int *seen = (int*)malloc(sizeof(int) * (n + 1));if (!seen) return;// 初始化标记数组for (int i = 0; i <= n; ++i) seen[i] = 0;while (p->next != NULL){int index = abs(p->next->data);         // 取绝对值if (seen[index] == 0){                  // 首次出现,做标记并前进seen[index] = 1;p = p->next;}else{                                  // 重复 -> 删除 p->nextNode *temp = p->next;p->next = temp->next;free(temp);}}free(seen);
}int main(){// head: 21 -> -15 -> -15 -> -7 -> 15Node *head = initList();push_back(head, 21);push_back(head, -15);push_back(head, -15);push_back(head, -7);push_back(head, 15);printf("原链表: ");printList(head);removeNode(head, 21);   // n 取绝对值上界(本例为 21)printf("删除后: ");printList(head);        // 期望输出:21 -15 -7return 0;
}
  1. 复杂度
  • 时间复杂度:一次线性扫描,每个结点 O(1) 处理 ⇒ O(n)(这里 n 指结点个数)。
  • 空间复杂度:标记数组 seen[0..n](这里的 n 为数据上界) ⇒ O(n)

4、反转链表

思路

  1. first指向空值,second指向1,third指向second的下一个节点

  1. 用second指向空值,然后挪,然后再让second指回1,再挪

  1. 直到second指向NULL,再加个头节点


```c
Node* reverseList(Node *head) {Node *first = NULL;         // 已反转部分的头Node *second = head->next;  // 待反转部分Node *third;                // 暂存下一节点while (second != NULL) {third = second->next;   // 暂存 nextsecond->next = first;   // 当前节点指向已反转部分first = second;         // first 前进second = third;         // second 前进}// 建立新的头结点Node *hd = initList();hd->next = first;return hd;
}
#include <stdio.h>
#include <stdlib.h>typedef struct Node {int data;struct Node *next;
} Node;// 初始化带头结点的链表
Node* initList(void) {Node *head = (Node*)malloc(sizeof(Node));head->data = 0;head->next = NULL;return head;
}// 创建新节点
Node* newNode(int x) {Node *p = (Node*)malloc(sizeof(Node));p->data = x;p->next = NULL;return p;
}// 尾插
void push_back(Node *L, int x) {Node *p = L;while (p->next) p = p->next;p->next = newNode(x);
}// 打印链表
void printList(Node *L) {Node *p = L->next;while (p) {printf("%d ", p->data);p = p->next;}printf("\n");
}/* ---------------- 链表反转核心函数 ---------------- */
Node* reverseList(Node *head) {Node *first = NULL;         // 已反转部分的头Node *second = head->next;  // 待反转部分Node *third;                // 暂存下一节点while (second != NULL) {third = second->next;   // 暂存 nextsecond->next = first;   // 当前节点指向已反转部分first = second;         // first 前进second = third;         // second 前进}// 建立新的头结点Node *hd = initList();hd->next = first;return hd;
}/* ---------------- 测试程序 ---------------- */
int main() {Node *head = initList();push_back(head, 1);push_back(head, 2);push_back(head, 3);push_back(head, 4);push_back(head, 5);printf("原链表: ");printList(head);Node *rev = reverseList(head);printf("反转后: ");printList(rev);return 0;
}

5、删除链表中间节点

  1. fast指向1,slow指向head

  1. fast走两步(fast=fast->next->next),slow走一步(slow=slow->next)

  1. 发现fast是NULL或者fast的下一个节点是NULL的时候,

int delMiddleNode(Node *head){Node *fast=head->next;Node *slow=head;while (fast!=NULL && fast->next!=NULL){fast=fast->next->next;slow=slow->next;}// 定义指针变量指向要删除的那个节点Node *q=slow->next;slow->next=q->next;free(q);return 1;
}

完整代码:

#include <stdio.h>
#include <stdlib.h>typedef struct Node {int data;struct Node *next;
} Node;// 初始化链表
Node* initList(void) {Node *head = (Node*)malloc(sizeof(Node));head->next = NULL;return head;
}// 尾插
void push_back(Node *L, int x) {Node *p = L;while (p->next) p = p->next;Node *q = (Node*)malloc(sizeof(Node));q->data = x;q->next = NULL;p->next = q;
}// 打印
void printList(Node *L) {Node *p = L->next;while (p) {printf("%d ", p->data);p = p->next;}printf("\n");
}// 删除中间节点
int delMiddleNode(Node *head) {if (head == NULL || head->next == NULL) return 0; // 空表Node *fast = head->next;Node *slow = head;while (fast != NULL && fast->next != NULL) {fast = fast->next->next;slow = slow->next;}Node *q = slow->next;slow->next = q->next;free(q);return 1;
}int main() {Node *head = initList();for (int i = 1; i <= 5; i++)push_back(head, i);printf("原链表: ");printList(head);delMiddleNode(head);printf("删除中间节点后: ");printList(head);return 0;
}

6、做个题目练练

效果如图所示:

  1. 找到中间的位置,断开

  1. 把后半截反转

  1. 6插到1和2中间,5插到2和3中间,4放到3后面.

利用四个指针 p1p2q1q2 实现两条链表的交叉合并。p1 指向第一条链表当前节点, p2 指向 p1 的下一个节点; q1 指向第二条链表当前节点, q2 指向 q1 的下一个节点。每次操作时,先将 q1 插入到 p1p2 之间(即 p1->next=q1q1->next=p2),使第二条链表当前节点嵌入第一条链表对应位置;然后四个指针整体后移(p1=p2q1=q2),继续进行下一轮插入。如此循环,直到任意一条链表遍历完为止,就能让两条链表的节点像拉链一样交错连接成一个完整的新链表

也就是

初始状态

A链表: Head → 123NULL  
B链表: 654NULL  指针:
p1 → 1
p2 → 2
q1 → 6
q2 → 5

下一步:把6插到1和2中间

操作:
p1->next = q1
q1->next = p2结果:
Head → 1623
B剩余:54更新指针:
p1 → 2
p2 → 3
q1 → 5
q2 → 4

下一步:把5插到2和3之间

操作:
p1->next = q1
q1->next = p2结果:
Head → 16253
B剩余:4更新指针:
p1 → 3
p2 → NULL
q1 → 4
q2 → NULL

下一步: 把4插到3后面

操作:
p1->next = q1
q1->next = p2 (此时p2为NULL)结果:
Head → 162534NULL更新指针:
p1 → NULL
q1 → NULL
循环结束

代码实现:

void reorderList(Node *head) {if (head == NULL || head->next == NULL) return;// ① 快慢指针找中点Node *fast = head->next;Node *slow = head;while (fast != NULL && fast->next != NULL) {fast = fast->next->next;slow = slow->next;}// ② 反转后半链表Node *first = NULL;Node *second = slow->next;slow->next = NULL;       // 截断前半部分Node *third = NULL;while (second != NULL) {third = second->next;second->next = first;first = second;second = third;}// ③ 交叉合并前后两半Node *p1 = head->next;Node *q1 = first;Node *p2, *q2;while (p1 != NULL && q1 != NULL) {p2 = p1->next;q2 = q1->next;p1->next = q1;q1->next = p2;p1 = p2;q1 = q2;}
}

整道题答案:

#include <stdio.h>
#include <stdlib.h>typedef struct Node {int data;struct Node *next;
} Node;Node* initList(void) {Node *head = (Node*)malloc(sizeof(Node));head->next = NULL;return head;
}void push_back(Node *L, int x) {Node *p = L;while (p->next) p = p->next;Node *q = (Node*)malloc(sizeof(Node));q->data = x;q->next = NULL;p->next = q;
}void printList(Node *L) {Node *p = L->next;while (p) {printf("%d ", p->data);p = p->next;}printf("\n");
}/* -------------------- 重新排列链表 -------------------- */
/*
目标:1→2→3→4→5  →  1→5→2→4→3
步骤:1. 快慢指针找到中点2. 反转后半部分链表3. 合并两部分链表
*/
void reorderList(Node *head) {if (head == NULL || head->next == NULL) return;// ① 快慢指针找中点Node *fast = head->next;Node *slow = head;while (fast != NULL && fast->next != NULL) {fast = fast->next->next;slow = slow->next;}// ② 反转后半链表Node *first = NULL;Node *second = slow->next;slow->next = NULL;       // 截断前半部分Node *third = NULL;while (second != NULL) {third = second->next;second->next = first;first = second;second = third;}// ③ 交叉合并前后两半Node *p1 = head->next;Node *q1 = first;Node *p2, *q2;while (p1 != NULL && q1 != NULL) {p2 = p1->next;q2 = q1->next;p1->next = q1;q1->next = p2;p1 = p2;q1 = q2;}
}/* -------------------- 测试 -------------------- */
int main() {Node *head = initList();for (int i = 1; i <= 5; i++) push_back(head, i);printf("原链表: ");printList(head);reorderList(head);printf("重新排列后: ");printList(head);return 0;
}

7、单链表的局限:

不可以回头

于是想个办法:

  1. 单向循环链表

循环链表(Circular Linked List)是另一种形式的链式存储结构。其特点是表中最后一个节点的指针域指向头节点,整个链表形成一个环

当链表遍历时,判别当前指针 p 是否指向表尾结点的终止条件不同。在单链表中,判别条件为 p != NULL 或 p->next != NULL,而循环链表的判别条件为 p != L 或 p->next != L

通常这么玩:

fast和slow先都指向head,fast走两步,slow走一步,如果两个指针可以相遇,说明有环

int isCycle(Node *head){Node *fast=head;Node *slow=head;while (fast!=NULL && fast->next!=NULL){fast=fast->next->next;slow=slow->next;if (fast==slow){return 1;}}return 0;
}
http://www.dtcms.com/a/455103.html

相关文章:

  • 素马网站建设费用差距用帝国做的网站
  • 天津个人网站备案查询c mvc 大型网站开发
  • 做英文网站的心得运营推广策略有哪些
  • 广东网站建设包括什么导购网站怎么做视频教学
  • 在冲突中,先尝试理解对方,而非急于反驳。理解,是沟通的桥梁。
  • 网站备案当面核验单色系网站设计有哪些
  • 都匀市网站建设深圳英迈思做网站好么
  • LangChain详解(二)
  • 礼品公司网站源码做网站交易装备可以么
  • Softmotion in CoDeSys2.3 User Manual-5
  • 域名和网站不是一家怎么办湛江市网站建设
  • 免费asp网站源码广州市番禺区
  • Vue3-OptionsAPI 与 CompositionAPI以及setup概述
  • 电商设备网站怎么做吉林省建设工程造价信息网
  • 做网站建设多少钱网站域名年龄
  • 网站建设的行业资讯组建网站开发团队
  • 营销型网站方案国人原创wordpress cms模板:hcms
  • wordpress 站点维护社交网站开发意义
  • 网站flash网页设计基础教程结课论文
  • 无网站可以做cpc吗零基础学网站建设 知乎
  • 网站关于我们怎么做单页面乐昌网站建设
  • RK3568入门之VScode远程连接开发板,直接开发板上面编程和实验
  • 六安做网站seowordpress放在二级目录下
  • 大型网站建设兴田德润实惠个人网站毕业设计论文
  • 网站app开发平台成都seo专家
  • 网站备案不注销有什么后果怎样用模块做网站
  • 江西网站建设公司联系方式oss做网站
  • 查公司的网站有哪些龙岗网站推广
  • 网站百度排名提升竞价推广教程
  • 程序员做网站类的网站犯法吗好用的a站