链表
目录
单链表
补充指针:举个例子,int num = 5;有个整型的变量num,存储整型数据5。int* p_num = #有个指针类型变量p_num,存储的是num变量的地址,所以我们可以说指针p_num指向变量num
-
顺序表—>静态存储分配—>事先确定容量
-
链表—>动态存储分配—>运行时分配空间
-
单链表:线性表的链接存储结构
-
存储思想:用一组任意(不连续、零散分布)的存储单元存放线性表的元素
-
单链表的存储特点:
-
逻辑次序和物理次序不一定相同
-
元素之间的逻辑关系用指针表示
-
-
单链表是由若干个结点构成的;单链表的结点只有一个指针域
typedef struct node{ DataType data; // 数据域 struct node *next; // 指针域 }Node,*Link; Node st; 等价于 struct node st; Link p; 等价于 struct node *p; // 一个指向结构体的指针(指针的本质实际上是一种特殊的数据类型,指针类型的变量存的就是地址) p=&st; // 申请一个存储单元 // 通过malloc()函数声明一块空间 用p指向这块空间 p = (Link)malloc(sizeof(Node)); 等价于 p = (struct node*)malloc(sizeof(Node)); // 引用数据元素 (*p).data 或者 p->data // 引用指针域 p->next
-
存储结构:重点在数据元素之间的逻辑关系的表示。所以,将实际存储地址抽象(单链表其实是零散在内存中的)
头指针:指向第一个结点的地址
尾标志:终端结点的指针域为空
-
上图中的空表和非空表的表示不一致,如何将其统一?
头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便空表和非空表统一处理
-
单链表的实现
-
遍历操作
void displayNode(Link head){ p = head->next; while(p!=NULL){ printf("%d",p->data); p=p->next; } }
-
求单链表的元素个数
int length(Link head){ p = head->next; int count = 0; while(p!=NULL){ count++; p=p->next; } return count; }
-
查找操作
bool queryNode(Link head,DataType x){ p = head->next; while(p!=NULL){ if(p->data==x){ printf("%d",p->data); return true; } p=p->next; } return false; }
-
插入操作
bool insertNode(Link head,int i,DataType x){ // 在i前面插入一个结点 p=head; // 注意这里和上面的代码有点不一样 因为插入的地方可能是在第一个元素之前 int count = 0; while(p!=NULL && count < i - 1){ count++; p=p->next; } if(p==NULL) return false; // 判断i-1处是否为NULL || p现在是否为空(还没到i-1) node = (Link)malloc(sizeof(Node)); // node存的是地址 node->data=x; node->next=p->next; p->next=node; return true; }
-
创建单链表
// 头插法:将待插入结点插在头结点的后面,得到的链表的顺序和数组的顺序是相反的(数组的尾部在前头部在后) Link newList(DataType a[],int n){ // 初始化头结点 head = (Link)malloc(sizeof(Node)); head->next=NULL; // 插入元素 for(int i = 0;i < n;i++){ node = (Link)malloc(sizeof(Node)); node->data=a[i]; node->next=head->next; head->next=node; } return head; // 只要知道开始位置就可以得到整个链表 } // 尾插法:将待插入结点插在终端结点的后面,得到的链表的顺序和数组的顺序是一样的 Link newList(DataType a[],int n){ // 初始化头结点和尾结点 head = (Link)malloc(sizeof(Node)); head->next=NULL; rear=head; // 插入元素 for(int i = 0;i < n;i++){ node = (Link)malloc(sizeof(Node)); node->data=a[i]; rear->next=node; rear=node; // 一直保持rear指向最后一个结点 } rear->next=NULL; // 一定要写! return head; }
-
删除结点
bool deleteNode(Link head,DataType x){ // 判断是否为空表 if(head == NULL || head->next == NULL){ return false; } // 删除结点是把要删除的结点的后一个结点赋给前一个结点的next指针 // 所以需要一直知道要删除的结点的前一个结点是什么(因为单链表只能获取后面的结点 不能获取前面的结点) // 初始化时 就保证p和q一前一后的关系 p=head->next; q=head; // 在查找的过程中,如果发现p所指向的结点data值不是要找的x,则p、q同时后移;一旦找到,则执行删除操作 while(p!=NULL){ if(p->data==x){ q->next=p->next; free(p); return true; } q=p; p=p->next; } return false; }
-
释放链表(将单链表中所有结点的存储空间释放)
void clearLink(Link head) { Link q; while (head != NULL) { // display(head); // 如果display()方法没有针对空链表进行判断的话,这个函数的调用不要在head=head->next后面。因为head=head->next可能使head为NULL,如果display()函数没有针对空链表判断的话,这里会报错 q = head; head = head->next; free(q); } }
-
循环链表
-
在单链表中无法找到指定结点的前一个结点,只能找到后一个结点
-
循环链表:将单链表的首尾相接,将终端终点的指针域由空指针改为指向头结点,构成单循环链表
-
循环链表的特点:
-
循环链表中没有明显的尾端,要注意防止死循环
循环条件: p!=NULL变成了p!=head p->next!=NULL变成了p->next!=head
-
双向链表
-
双向链表:在单链表的每个结点中再设置一个指向其前驱结点的指针域
-
结点结构:
-
data:数据域,存储数据元素
-
prior:指针域,存储该结点的前驱结点地址
-
next:指针域,存储该结点的后继结点地址
-
单链表:学生管理系统
-
这种系统只能在devc++上跑,不能在clion上跑(clion会把所有的scanf输入全部输入了之后才输出,用cin倒是没问题
#include<stdio.h> #include<stdlib.h> #include<string.h> int choose; int id; char name[10]; typedef struct student { int id; char name[10]; } Student; typedef struct node { struct student data; struct node *next; } Node, *Link; void showMenu() { printf("------------欢迎来到学生管理系统------------\n"); printf("----------------1.新增学生----------------\n"); printf("----------------2.查询学生----------------\n"); printf("----------------3.删除学生----------------\n"); printf("----------------4.修改学生----------------\n"); printf("--------------5.打印全部学生---------------\n"); printf("----------------6.清空学生----------------\n"); printf("请选择(1-6):"); } Student createStudent() { Student s; printf("请输入学号:"); scanf("%d", &s.id); printf("请输入姓名:"); scanf("%s", s.name); return s; } int inputId() { printf("请输入要查询的学生的学号:"); scanf("%d", &id); return id; } int updateId() { printf("请输入要修改的学生的学号:"); scanf("%d", &id); return id; } int deleteId() { printf("请输入要删除的学生的学号:"); scanf("%d", &id); return id; } char *inputName() { memset(name, 0, sizeof(name)); printf("请输入要修改的学生的姓名:"); scanf("%s", &name); return name; } // 插入学生 并保证学号递增 bool insertStudent(Link head, Student st) { Link node; Link p = head->next; Link q = head; while (p != nullptr) { if (p->data.id > st.id) { node = (Link) malloc(sizeof(Node)); node->data.id = st.id; for (int i = 0; i < strlen(st.name); ++i) { node->data.name[i] = st.name[i]; } node->next = p; q->next = node; return true; } q = p; p = p->next; } // 到了末尾 node = (Link) malloc(sizeof(Node)); node->data.id = st.id; for (int i = 0; i < strlen(st.name); ++i) { node->data.name[i] = st.name[i]; } q->next = node; // p这个时候是null q是最后一个元素 node->next = nullptr; return true; } void selectStudent(int s_id, Link head) { Student s; Link p = head->next; while (p != nullptr) { if (p->data.id == s_id) { printf(("学生的学号为:%d,姓名为:%s\n"), p->data.id, p->data.name); return; } p = p->next; } printf("未找到该学生!"); } bool deleteStudent(int s_id, Link head) { Link p = head->next; Link q = head; while (p != nullptr) { if (p->data.id == s_id) { q->next = p->next; free(p); return true; } p = p->next; } return false; } // 修改学生 不允许修改学号 bool updateStudent(char s_name[10], int s_id, Link head) { Link p = head->next; while (p != nullptr) { if (p->data.id == s_id) { for (int i = 0; i < strlen(s_name); ++i) { p->data.name[i] = s_name[i]; } return true; } p = p->next; } return false; } void printAllStudent(Link head) { Link p = head->next; while (p != nullptr) { printf(("学生的学号为:%d,姓名为:%s\n"), p->data.id, p->data.name); p = p->next; } } bool clearAllStudent(Link head) { Link p; while(head != nullptr){ p = head; head = head->next; free(p); } return true; } int main() { // 创建头结点 Link head = (Link) malloc(sizeof(Node)); head->next = nullptr; while (true) { showMenu(); scanf("%d", &choose); switch (choose) { case 1: insertStudent(head, createStudent()); break; case 2: selectStudent(inputId(), head); break; case 3: deleteStudent(deleteId(), head); break; case 4: updateStudent(inputName(), updateId(), head); break; case 5: printAllStudent(head); break; case 6: clearAllStudent(head); return 0; default: printf("请输入正确的数字!"); break; } system("pause"); system("cls"); } }
约瑟夫环
介绍
-
约瑟夫问题:
- 有n 只猴子,按顺时针方向围成一圈选大王(编号从1到n ) ,从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n, m后,输出最后猴王的编号。
循环链表实现
#include<iostream>
using namespace std;
typedef struct node {
int data;
struct node *next;
} *Link, Node;
int main() {
int n, m; // n是猴子个数 m是报数
Link head = (Link) malloc(sizeof(Node)); // sizeof()的参数应该是整个结构体 malloc()的结果是一个地址 可以强转成Link类型
head->next = nullptr;
while (true) {
cin >> n >> m;
if (n == 0 || m == 0) { // 当n或m为0时 则退出
free(head); // 释放head
break;
}
// 尾插法构建链表
Link tail = head;
for (int i = 1; i <= n; i++) {
Link q = (Link) malloc(sizeof(Node));
q->data = i;
tail->next = q;
// 形成循环链表
q->next = head->next;
tail = q;
}
// 维持p、q一前一后的关系 方便删除
Link q = tail;
Link p = head->next;
int i = 1; // 计数
while (p != q) { // 当p和q不重叠的时候就进行循环
if (i == m) { // 报数等于m时 删除节点
q->next = p->next;
free(p);
// 将p移动到下一个有效的节点上
p = q->next;
// 重新报数
i = 1;
} else {
i++;
q = p;
p = p->next;
}
}
cout << p->data << endl;
free(p); // 清除该节点
head->next = nullptr; // 头结点的next赋为空
}
return 0;
}、
数组标志位实现
#include <iostream>
using namespace std;
int main(){
int n,m; // n表示猴子个数 m表示报数
while(true){
cin >> n >> m;
if (n == 0 || m == 0){
return 0;
}
int arr[310] = {0};
// 赋初值 数组里存的是猴子的编号(从1开始
for (int i = 0; i < n; ++i) {
arr[i] = i + 1;
}
int num = n; // 防止修改n
int i = 0; // 用于遍历下标
int count = 1; // 用于计报数
while(num > 1){
if (arr[i] != 0){ // 该数组对应的位数不为0 报数
if (count == m){
num--;
arr[i] = 0; // 为0表示该处的猴子已经淘汰了
i = (i + 1) % n;
count = 1;
}else{
i = (i + 1) % n;
count++;
}
}else{ // 该数组对应的位数为0 就取下一个
i = (i + 1) % n;
}
}
for (int j = 0; j < n; ++j) {
if (arr[j] != 0){
cout << arr[j] << endl;
}
}
}
}、
数组链接方式实现
用数组模拟循环链表
#include <iostream>
using namespace std;
int main(){
int n,m;
while(true){
cin >> n >> m;
if (n == 0 || m == 0){
return 0;
}
int arr[310] = {0}; // 该数组中存的内容是该位置上的元素指向的下一个元素的下标
for (int i = 0; i < n - 1; ++i) {
arr[i] = i + 1; // 存的是下一个元素的下标
}
arr[n - 1] = 0;
// 和链表一样 需要维护两个指针 一前一后
int pos = 0;
int prior = n - 1;
int num = n; // 用于计算还剩几个元素没被淘汰
int count = 1; // 用于计数
while (num > 1){
if (count != m){ // 没有到m的时候 则移动指针
prior = pos;
pos = arr[pos]; // 时刻记住arr存的是指向的下一个元素的下标
count++;
}else{
num--;
count = 1;
arr[prior] = arr[pos]; // 更改链接关系
arr[pos] = -1; // 淘汰的猴子置为-1
pos = arr[prior]; // pos指向下一个有效的位置
}
}
// 下面两种输出方法都可以
// 此时的pos(prior和pos是一样的)就存的是最后一个猴子的下标 因为编号从1开始 所以记得+1
cout << pos + 1 << endl;
// for (int i = 0; i < n; ++i) {
// if (arr[i] != -1){
// cout << i + 1 << endl;
// break;
// }
// }
}
}
数学方法
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
int n,m,i,s=0;
scanf("%d%d",&n,&m);
for (i=2; i<=n; i++)
s=(s+m)%i;
cout<<s+1<<endl;
return 0 ;
}
多项式加法
输出多项式
#include <iostream>
using namespace std;
int main() {
int n;
int a;
while (cin >> n) {
for (int i = n; i >= 0; --i) { // n就是x的次方
cin >> a;
if (a == 0){ // 当a等于0时 直接跳过
continue;
}
// 首先处理符号
if (i == n){ // 最高位 题目明确说了最高位的系数不为0
if (a < 0){
cout << "-";
}
}else{
if (a < 0){
cout << "-";
}else{
cout << "+";
}
}
// 然后处理数值
if (i == 0){ // 最后一位 即x=0时 -1/1都是要输出的
cout << abs(a);
}else{
if (a == 1 || a == -1); // 当a为-1/1时 不需要输出值
else{
cout << abs(a);
}
}
// 最后处理次数
if (i != 0){ // i等于0时 不用考虑次数的输出
cout << "x";
if (i != 1){
cout << "^" << i;
}
}
}
cout<<endl;
}
return 0;
}
多项式加法—数组实现
#include <iostream>
using namespace std;
void print(int a[], int n) {
for (int i = n; i >= 0; --i) {
if (a[i] == 0) continue; // 等于0则跳过
// 处理符号
if (i == n) { // 最高位
if (a[i] < 0) {
cout << "-";
}
} else { // 其他位
if (a[i] < 0) {
cout << "-";
} else if (a[i] > 0) {
cout << "+";
}
}
// 处理系数
if (i == 0) {
cout << abs(a[i]);
continue;
}
if (abs(a[i]) != 1) { // 系数为1的绝对值的时候 不输出
cout << abs(a[i]);
}
// 处理指数
cout << "x";
if (i != 1) { // 当指数不为1时 则输出
cout << "^" << i;
}
}
cout << endl;
}
int main() {
int a[101] = {0}; // 存第一个多项式的系数,下标则为指数
// int b[101] = {0}; // 存第二个多项式的系数,下标则为指数
int maxA = 0; // 第一个多项式的最高指数
// int maxB =0; // 第二个多项式的最高指数
cout << "请输入第一个多项式的系数和指数,以(0 0)结束,指数的范围在0~101之间" << endl;
while (true) {
cout << "请输入系数和指数:";
int co, index; // co系数 index指数
cin >> co >> index;
if (co == 0 && index == 0) {
break;
}
a[index] += co;
if (index >= maxA) {
maxA = index;
}
}
cout << "请输入第二个多项式的系数和指数,以(0 0)结束,指数的范围在0~101之间" << endl;
while (true) {
cout << "请输入系数和指数:";
int co, index; // co系数 index指数
cin >> co >> index;
if (co == 0 && index == 0) {
break;
}
a[index] += co;
if (index >= maxA) {
maxA = index;
}
}
print(a, maxA);
return 0;
}
多项式加法—链表实现
#include <iostream>
using namespace std;
typedef struct node {
int co; // 系数
int index; // 指数
struct node *next;
} Node, *Link;
// 打印链表
void print(Link head) {
if (head->next == nullptr) {
return;
}
Link p = head->next;
// 先处理最高项
if (p->co < 0) {
cout << "-";
}
if (abs(p->co) != 1){
cout << abs(p->co);
}
cout << "x";
if (p->index != 1){
cout << "^" << p->index;
}
p=p->next;
// 处理其他项
while (p != nullptr) {
if (p->co == 0) continue;
else if (p->co > 0) {
cout << "+";
} else if (p->co < 0) {
cout << "-";
}
if (p->index == 0) { // 当前p指向指数为0的
cout << abs(p->co) << endl;
break;
}
if (abs(p->co) != 1) { // 系数的绝对值不等于1时 输出
cout << abs(p->co);
}
cout << "x";
if (p->index != 1) { // 指数不为1时
cout << "^" << p->index;
}
p = p->next;
}
cout << endl;
}
// 构建/插入链表
void createList(Link head, Link num) {
if (head->next == nullptr) {
head->next = num;
num->next = nullptr;
return;
}
// 维护两个节点 一前一后的关系 以便插入
Link p = head->next;
Link q = head;
// 构建链表 按指数从大到小排列
while (p != nullptr) {
if (num->index > p->index) { // 当要插入的节点的指数比p节点的指数大时 就该插在p前面q后面
num->next = p;
q->next = num;
return;
} else if (num->index == p->index) { // 当要插入的节点的指数和p节点的指数相等时 合并系数
p->co += num->co;
return;
}
q = p;
p = p->next;
}
// 如果到末尾了 还未插入 则为最后一个节点
q->next = num;
num->next = nullptr;
}
// 释放函数
void destroy(Link head){
// 维护p q一前一后的关系
Link p = head->next;
Link q = head;
while(p != nullptr){
q->next = p->next;
free(p);
p = q->next;
}
free(q);
}
int main() {
Link head = (Link) malloc(sizeof(Node));
head->next = nullptr;
int a, b;
cout << "请输入第一个多项式的系数和指数,以(0 0)结束,指数的范围在0~101之间" << endl;
while (true) {
cin >> a >> b;
if (a == 0 && b == 0) {
break;
}
Link num = (Link) malloc(sizeof(Node));
num->next = nullptr;
num->co = a;
num->index = b;
createList(head, num);
}
cout << "第一个多项式为:";
print(head);
cout << "请输入第二个多项式的系数和指数,以(0 0)结束,指数的范围在0~101之间" << endl;
while (true) {
cin >> a >> b;
if (a == 0 && b == 0) {
break;
}
Link num = (Link) malloc(sizeof(Node));
num->next = nullptr;
num->co = a;
num->index = b;
createList(head, num);
}
cout << "合并后的多项式为:";
print(head);
destroy(head);
return 0;
}