文章目录
一 单链表
1.1什么是链表
链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。
它是以结构体为节点,将一个结构体看成数据域和指针域两个部分,数据域用于存储数据,指针域用于连接下一个节点
.
1.2链表的特点
1、链表没有固定的长度,可以自由增加节点
2、链表能够实现快速的插入删除数据,也就是可以快速的插入和删除链表中的节点
3、与数组类似,链表也是一种线性数据结构
4、链表的尾结点的后继必定指向空
1.3单链表的结构示意图
1.4单链表结构体的声明
typedef int Type; //数据类型 通过取别名的形式进行灵活使用
struct Node{
Type data; //链表节点的数据域,用于存储数据
struct Node *next; //链表节点的指针域,用于指向和连接下一个节点
};
1.5单链表的功能
1、创建单链表节点
//1.创建单链表节点,开辟内存空间
Node * list_init(Type data)
{
Node * temp = (Node *)malloc(sizeof(Node));
if (NULL == temp)
{
ERROR("节点创建失败");
//assert(temp); //错误检查函数,函数参数为假就报错
return NULL;
}
temp->data = data; //给数据域赋值
temp->next = NULL; //初始化next指针为空,指向空
return temp;
}
2、链接单链表节点
//链表各个节点的内存不是连续的
Node * list_create_end(Type arr[], int n)
{
Node * head = NULL;
Node * end = NULL;
for (int i = 0; i < n; i++)
{
if (NULL == head)
{
head = list_init(arr[i]);
end = head;
continue;
}
//从尾部链接两个节点
end->next = list_init(arr[i]);
end = end->next;
}
return head;
}
3、单链表的打印输出
void list_print(Node * head)
{
for (Node * temp = head; temp != NULL; temp = temp->next)
{
printf("%d ->", temp->data);
}
printf("NULL\n");
}
4、单链表节点的插入
插入元素 头 中间 尾部
插入头结点之后 当做首元结点 插入到链表中间的某个位置
插入到链表的最末端,作为链表中最后一个数据元素
两个操作:a.将新结点的next指针指向插入位置后的结点.
b.将插入位置前结点的next指针指向插入结点.
void list_insert(Node * head, int index, Type data)
{
Node * temp = head;
if (0 == index)
{
Node * newNode = list_init(data);
newNode->next = head->next;
head->data = data;
head->next = newNode;
return;
}
for (int i = 0; i < index - 1; i++)
{
assert(temp);
temp = temp->next;
}
Node * newNode = list_init(data);
newNode->next = temp->next;
temp->next = newNode;
}
5、单链表节点的删除
删除元素
(1)将结点从链表上摘下来
(2)手动释放结点 回收内存空间
void list_delete(Node * head, int index)
{
Node * temp = head;
if (0 == index)
{
Node * temp2 = head->next;
head->data = head->next->data;
head->next = head->next->next;
free(temp2);
return;
}
for (int i = 0; i < index - 1; i++)
{
assert(temp);
temp = temp->next;
}
Node * old = temp->next;
temp->next = old->next;
//temp->next = temp->next->next;
free(old);
}
6、通过下标获取单链表指定位置上的数据
Node * list_get(Node * head, int index)
{
Node * temp = head;
for (int i = 0; i < index - 1; i++)
{
assert(temp);
temp = temp->next;
}
return temp;
}
7,修改数据
//更新元素
Node *amendElem(Node *p, int add, int newElem)
{
Node*temp = p;
temp = temp->next;//遍历之前,temp指向首元结点
for (int i = 1; i < add; i++)
temp = temp->next;
temp->elem = newElem;
return p;
}
二 双向链表
2.1什么是双向链表
双链表是链表的一种,和单链表一样,也是把多个结构体节点用结构体指针连接起来
,只不过单链表只有一个指向下一个节点的结构体指针,而双链表有两个结构体指针分别指向上一个节点和下一个节点。
2.2双向链表的特点
1、双链表和单链表一样,没有固定的长度,可以自由增加节点
2、双链表有两个指针域,一个用于指向前一个节点,另一个用于指向下一个节点。
3、由于有指向前一个节点的指针域,双链表可以很好的支持逆序输出
2.3双链表的结构示意图
2.4双向链表结构体的声明
typedef struct Link
{
int elem;//代表数据域
struct Link *next;//代表着指针域,指向 直接后继 元素
}link;//这个结构体数据类型
2.5双链表的功能
1、创建双链表节点
创建头结点 头指针指向头节点
temp指向头结点
创建下一个节点 下一个节点的地址给temp->next
temp指向下一个节点的地址
link *initLink()
{
link *p = (link*)malloc(sizeof(link));//创建头节点
link *temp = p;//指向头结点
//生成链表
for (int i = 1; i < 10; i++)
{
link *a = (link*)malloc(sizeof(link));
a->elem = i;// rand() % 100;
a->next = NULL;
//将temp节点与新建立的a节点建立逻辑关系
temp->next = a;//指向下一个节点
temp = temp->next;//指针temp每次都指向新链表的最后一个节点,其实就是a节点,这里写temp=a也是可以的
}
return p;
}
2、链接双链表的插入
插入元素
头 中间 尾部
插入头结点之后 当做首元结点
插入到链表中间的某个位置
插入到链表的最末端,作为链表中最后一个数据元素
两个操作:1.将新结点的next指针指向插入位置后的结点.
2.将插入位置前结点的next指针指向插入结点.
link * insertElem(link *p, int elem, int add)
{
link *temp = p;
for (int i = 1; i < add; i++)
{
temp = temp->next;
if (temp == NULL)
{
printf("插入位置无效\n");
return p;
}
}
//创建插入结点c
link *c = (link*)malloc(sizeof(link));
c->elem = elem;
//向链表中插入结点
c->next = temp->next;
temp->next = c;
return p;
}
3、双链表的打印输出
void display(link *p)
{
link *temp = p;//游标
while (temp->next)//非0
{
temp = temp->next;
printf("%d ", temp->elem);
}
printf("\n");
}
4、双链表节点的删除
(1)将结点从链表上摘下来
(2)手动释放结点 回收内存空间
temp->next=temp->next->next
link *delElem(link *p, int add)
{
link *temp = p;
//遍历到被删除结点的上一个结点
for (int i = 1; i < add; i++)
{
temp = temp->next;
if (temp->next == NULL)
{
printf("没有该结点\n");
return p;
}
}
link *del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
temp->next = temp->next->next;
free(del);//释放该结点,防止内存泄露
return p;
}
5、双链表的查找
//查找元素
int selectElem(link *p, int elem)
{
link *t = p;
int i = 1;
while (t->next)
{
t = t->next;
if (t->elem == elem){
return i;
}
i++;
}
return -1;//执行到这里,表示查找失败
}
6、双链表的修改
//更新元素
link *amendElem(link *p, int add, int newElem)
{
link *temp = p;
temp = temp->next;//遍历之前,temp指向首元结点
for (int i = 1; i < add; i++)
temp = temp->next;
temp->elem = newElem;
return p;
}
三 练习-约瑟夫环
已知n个人 围坐在一张圆桌周围 从k开始报数
数到m的那个人出列 他的下一个人又重新数1
从3开始数 数到2出列
4 1 3 2 断掉 释放节点
代码实现
#include<malloc.h>
#include<stdlib.h>
#include<stdio.h>
typedef struct node
{
int number;//人数
struct node *next; //指针域
}person;
person * initLink(int n)//初始化
{
person * head = (person*)malloc(sizeof(person));
head->number = 1;//从1开始
head->next = NULL;
person * cur = head;//指向头结点
for (int i = 2; i <= n; i++)
{
person * body = (person*)malloc(sizeof(person));
body->number = i;
body->next = NULL;
cur->next = body;//将cur节点与新建立的body节点建立逻辑关系
cur = cur->next;//可以写cur = body
}
cur->next = head;//首尾相连
return head;
}
void last(person * head, int k, int m)//游戏进行
{
person * tail = head;//指向头结点
while (tail->next != head)//一圈
{
tail = tail->next;
}
person *p = head;//指向头结点
while (p->number != k)//找编号为k
{
tail = p;
p = p->next;
}
while (p->next != p)
{
for (int i = 1; i < m; i++)//报数
{
tail = p;
p = p->next;
}
tail->next = p->next;
printf("出列人的编号为:%d\n", p->number);
free(p);
p = tail->next;//游戏继续 下一个人开始
}
printf("出列人的编号为:%d\n", p->number);
free(p);
}
int main()
{
printf("输入圆桌上的人数n:");
int n;
scanf("%d", &n);
person *head = initLink(n);
printf("从第k人开始报数(k>1且k<%d):", n);
int k;
scanf("%d", &k);
printf("数到m的人出列:");
int m;
scanf("%d", &m);
last(head, k, m);
system("pause");
return 0;
}
结果展示
四 总结
1.链表和数组的区别:
数组和顺序表是顺序存储的,也就是内存是连续的;而链表是通过指针将不连续的内存连接起来,实现链式存储的。
2.双链表和单链表的区别:
单链表只有一个指针域,用于指向下一个节点的首地址,而双链表有两个指针域,可以分别指向前一个节点和下一个节点,链表的遍历更加方便。