单链表
- 0.头文件
#define _CRT_SECURE_NO_DEPRECATE //scanf
#include<stdio.h>
#include<stdlib.h>//用于malloc建立结点
1.定义结构体
typedef int ElemType;//数据元素类型
typedef struct Node {//定义单链表结构类型
ElemType data;//数据域
struct Node* next;//指针域,指向结点的指针
}*List;
2.建立头结点和尾插入法
建立头结点,用尾插入法插入结点,再定义一个指针r,永远指向新建的结点p,前一个指针r的next一直指向后面新建的结点,然后自己定义链表长度,for循环依次输入数据。
如果是和我一样void定义函数的,有想过参数List &L为什么要加&吗?(本页最后解答)
void CreateList(List &L,int n) //List &L就相当于 Node* &L
{
L = (Node*)malloc(sizeof(Node));//建立头结点,并使L指向头结点(数据域和指针域都是空的计数要从下一个结点计数)
if (!L) {
printf("存储失败");
L->next = NULL;//初始化头地址
}
Node *r, *p;
r = L;//r指针指向头结点
printf("请输入:\n");
for (int i = 0; i<n; i++)//尾插入法
{
p = (Node*)malloc(sizeof(Node));
scanf_s("%d",&p->data);//把输入的值给结点数据
p->next = NULL;
r->next = p;//前一个指针指向下一个结点
r = p;//指向当前结点
}
}
3.打印链表
p指针直接遍历链表,输出数据域即可。
void Print(List L)///打印输出
{
Node* p;//定义一个新指针
p = L->next;//指针指向L的第一个结点
printf("链表为:");
while (p)//当p不等于空
{
printf("%d ", p->data);//将输入的值放入数据域
p = p ->next;//p指向下一个指针
}
}
4.按内容查找
p指针遍历链表到p->next=null为止,while循环判断是不是需要的数据即可。
void Find(List L, ElemType e)//按内容查找
{
Node* p;
p = L ;//p指针指向L的第一个指针
int i=0;
while (p && p->data!=e) {//当p不等于空,输入的值不等于想要查找的值,循环
p = p->next;//指向下一个结点的指针域
i++;//计数
}
if (p ==NULL) printf("查找错误");
else printf("\n(1)按内容查找%d的是第%d位\n",e,i);
}
5.按位置查找
p指针遍历链表,当j=i-1时候,p指针指向第i个结点
void FindTwo(List L, int i)//按位置查找
{
Node* p;
p = L ;//p指向第一个结点
int j = 0;
while (p && j< i)//p不等于空并且当j=i-1时候p指针指向第i个结点
{
p = p->next;
j++;
}
if (p == NULL) printf("查找错误");
printf("\n(3)按位置查找的第%d位数据是:%d\n",j,p->data);
}
6.插入结点(头插入法)
先找到需要插入结点的前一个结点p,然后让新结点r指向原结点p->next(就是你想插入的位置的结点),再让原结点的前结点p连接新结点r。
void Insert(List L, int n, ElemType a) //插入(头插入法)
{
Node* p,*r;
p = L;//p指针指向头指针L
int i=0;
while (p && i<n-1)//当p不为零,i++向下查找当i=n-2时候,p指针指向第n-1项
{ //要是想要插在第i个位置需要在第n结点之前插入(也就是第n-1的后面)
p = p->next;
i++;
}
if (i != n-1)printf("插入错误");
r= (Node*)malloc(sizeof(Node));//开辟新节点
r->data = a;//将需要插入的值输入到新节点的数据域
r->next = p->next;//因为原来的p->next(当前p的位置在n-1处)指向第n个结点就是L,所以新节点r->next指向的也是就是L
p->next = r;//在n-1处的p->next指向新节点r
printf("\n(2)在第%d位插入%d后\n", n,a);
}
7.按内容删除
定义两个指针,第一个指针p根据内容查找指向需要删除的结点,第二个指针r指向需要删除的结点的前一个,然后让前一个结点直接指向 需要删除的结点 的后一个结点。
void Delete(List L, ElemType x)//按照内容删除
{
Node *r, *p;
p = L;
r = L;//定义两个指针都指向L的第一个结点
int i = 0;
int j = 0;
while (p && p->data != x)//第一个指针指向查找的结点
{
p = p ->next;
i++;
}
while (r && j < i-1 )//第二个指针指向查找的结点的前一个
{
r = r->next;
j++;
}
if (j != i-1) printf("删除错误");
r->next =p->next;//前一个结点指针直接指向后一个结点
printf("\n(4)按内容删除的位置为%d,值为%d的结点\n",i,x);
free(p);//释放删除结点所占空间
}
8.按位置删除
和按内容删除差不多,就是直接找到需要删除的结点的前一个p,然后前一个结点p直接指向 需要删除的结点r 的后一个结点。
void DeleteTwo(List L, int n)//按位置删除
{
Node* p,*r;
p = L;//定义p指向头指针
int i = 0;
while (p && i < (n-1))//p不为空,i找到第n-1个结点
{
p = p->next;
i++;
}
if (i != n - 1)printf("删除错误");
r = p->next;//r指向第n个结点
p->next = r->next;//在第n-1个结点直接指向n+1个结点
printf("\n(5)按位置删除第%d个值为%d的结点\n",n,r->data);
free(r);//释放删除结点所占空间
}
9.主函数
int main()
{
List L;
ElemType x=8;
CreateList(L, 5);//长度为5的链表
Print(L);//打印
printf("\n");
Find(L, 4);//按内容查找4为第几位
Insert(L, 2, x);//插入位置为 2 值为 x的结点
Print(L);
printf("\n");
FindTwo(L, 3);//查找第三位
Delete(L,x);//删除刚才插入的值为x结点
Print(L);//
printf("\n");
DeleteTwo(L, 2);//删除位置为2的结点
Print(L);
printf("\n");
}
10.关于&L:
List L就相当于Node*L,CreateList接收的是主函数里L的一份拷贝,当CreateList运行完时会销毁L,实参L并没有发生改变,这是为什么呢?
因为在单链表中的头结点(实际就是头指针),L就是指向头指针的指针,申请的结空间会泄露,因为L这个指针被当做值传参(以传值的形式作为参数的变量在函数体内被修改之后,出了函数体就会失效,准确的说这个变量没被修改)那如何解决呢?
要是想修改L的内容,就需要修改指向L的指针的内容,因为L本身就是指针,所以需要修改的是指向指针的指针内容也就是Node*&L,如果传的是Node &L也是不行的,因为&L也会被当做拷贝,只能是Node*&L(*&L:指向L的指针指向的内容)这就是引用传参,因为它是直接将L作为参数传入进去,不会对L进行复制,既然传入进去的是L,那么对L的修改肯定也是有效的。
11.我要说一下我编译时发现的自己知识的不足(p指针指向)
p=L->next和p=L是两个事情,第一个p指的是第一个结点,第二个p指的是头节点。
之前我总是把p=L->next当做p指向头指针的指针域,导致我在后面的操作中出现蝴蝶效应,从而百思不得其解,其实这种说法也对只不过在链表建成之前也就是还是结点的时候可以这么想,在已经建成的链表中就应该想的是指向下一个结点。
最后运行结果如下