序言
下学期学数据结构,这学期老师让复习C语言,先练练单链表.。方便理解起见,设为int类型的单链表,
代码正文(包含了链表的初始化,查询,尾部新增,插入,删除,排序)
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;
struct node *next;
}node;
node *creatLink()
{
printf("开始创建 int 类型的链表,请输入您要存放到链表中的值:(输入 0 为结束的标志)\n");
node *head = malloc(sizeof(node)); //1. 申请一块头结点内存.
head->next = NULL; //将头结点的指针先置为NULL,
//如果用户不输入的话,头节点一个节点就是一整个链表(有空头结点,为节点的指针为NULL)
//使用尾插法,所以命名为 *rear
node *rear = head; //尾插指针rear 首先指向链表最尾端:head;
int temp;
while(scanf("%d",&temp), temp!=0)
{
node *t = malloc(sizeof(node)); //开辟新空间
t->data = temp; //将输入值赋予新空间的data
rear->next = t; //rear 的 next 指针指向新空间
rear = t; //rear 后移到 t 上.
}
rear->next = NULL;
return head; //将头结点指针返回给主函数
}
void printList(node *head)
{
node *cur=head->next;
while(cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
}
int query(node *head)
{
int num_query,i;
printf("输入您要查询的值:\n");
scanf("%d",&num_query);
node *pointer = head->next; //直接跳过传入的空节点指针.
for(i = 1; pointer != NULL; i++)
{
if(pointer->data == num_query)
{
printf("您要查询的值在链表中的第%d个节点\n",i);
return i;
}
pointer = pointer->next;
}
printf("抱歉,没查到.\n");
return -1;
}
void insert(node *head)
{
printf("请输入您想要在第___个节点后插入:");
int num, i = 1, newData;
node *temp = head->next;
scanf("%d",&num);
printf("请输入您想要插入的数值");
scanf("%d", &newData);
while(temp != NULL)
{
if(i==num) //得到节点数,在这个节点后进行插入.
{
node *newNode = malloc(sizeof(node));
newNode->data = newData;
newNode->next = temp->next;
temp->next = newNode;
return ;
}
i++;
temp=temp->next;
}
printf("您输入的节点数过大,大于了链表的节点数:%d",i);
}
void deleteNode(node *head)
{
int num_delNode, i = 1;
printf("请输入您要删除的节点的位置:");
scanf("%d",&num_delNode);
node *cur=head->next, *pre = head;
while(cur != NULL)
{
if(i==num_delNode) //得到节点数,对这个节点进行删除.
{
pre->next = cur->next;
free(cur);
return ;
}
i++;
pre = cur;
cur = cur->next;
}
printf("您输入的节点数过大,大于了链表的节点数:%d",i);
}
void swap(node *node1, node *node2)
{
int temp = node1->data;
node1->data = node2->data;
node2->data = temp;
}
void linkSort(node* head)
{
//使用冒泡排序
node *node1,*node2;
for(node1 = head->next; node1 != NULL; node1 = node1->next)
{
for(node2 = node1->next; node2 != NULL; node2 = node2->next )
{
if(node1->data > node2->data)
{
swap(node1,node2);
}
}
}
}
int main()
{
node *head; //创建头节点指针
head = creatLink(); //进行创建并初始化链表
//printList(head); //输出测试
//query(head); //查询操作.
//insert(head);
//deleteNode(head);
linkSort(head);
printList(head);
return 0;
}
运行效果如下:
什么是单链表?为什么使用它?
.(图盗百度百科的,先有个印象,再看解释,看不懂图完全没问题)
便于理解:先于"简单"的 数组 进行比较.
因为数组在内存空间为一整块连续的内存,所以数组可以很快的查询(通过下标或者指针)与尾加入。但是删除,插入时,都非常麻烦(时间复杂度较高).
举个例子:
插入:现在有一个长度为N(N>10000)的int 型数组,我要在头部插入多个 1。那么我需要让之后所有的数值都后退多位,且不可保证预申请的内存是否足够.
删除:或者还是这个数组。我要删除前1000位中任意多个,那么后面的大量数据要前移多位。效率及其低下。
现在来看单向链表,单向链表的基本组成部分为节点(node)。
每个节点包含:一个数值和一个指向下一个节点的节点指针。
当我们插入第 n 个节点后插入一个值时,只需要让第 n 个节点的 原指向 第 n+1 个节点的指针指向我们新申请的节点,新申请节点的指针 指向第n+1个节点的地址即可。
效果如图
插入前的情况
开始改变待插入节点的的指针。使其指向 第 n+1 个节点.
(先改变待插入节点的指针原因为:如果先改变第n个节点的指针,那么程序就失去了第n+1个节点的地址.要么代码量增加,要么内存泄漏)
接着改变第 n 个节点的指针
插入完成。整个操作中只对两个节点进行了改动。
删除与插入完全逆序.
单链表的几个要点
接下来说一下单链表的几个重点:(懂了思想之后再去写代码,而不是一口气写一大堆,改都不会改.)
1.单链表的头指针一旦指向了链表,请务必不要再进行改动了(唯一的情况是销毁链表,单向链表请务必保证从尾部向头部销毁,不然有可能内存泄漏)
2.单链表尽可能 (请务必这样做) 拥有一个空的头节点,空的头结点的意思是:头结点不存放数值data,只是单纯的指向下一个节点.(好处会在后面的代码中体现,并进行对比.)
3.单链表的尾部节点的指针必须置为NULL,这样不仅有了判断链表是否结束的标志,而且可以防止野指针的出现(只有上帝知道它会不会指向非常重要的地方)
4.单链表的插入,删除固然快,但是查询速度和查询方法以及代码量。。。(后面代码中会详细解释),
现在看这幅图有没有明白一些呢?
开始代码施工
再说一次,每个节点应该包含一个数值(data)与一个指向下一节点的节点指针( *next).
结构体代码如下
typedef struct node //定义节点类型
{
int data; //一个数值
struct node *next; //一个指向下一个节点的节点指针
}node;
然后,节点与节点之间互相连接起来就是链表。现在开始建立一个新的 Project 测试。
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;
struct node *next;
}node;
node *creatLink()
{
printf("开始创建 int 类型的链表,请输入您要存放到链表中的值:(输入 0 为结束的标志)\n");
node *head = malloc(sizeof(node)); //1. 申请一块头结点内存.
head->next = NULL; //将头结点的指针先置为NULL,
//如果用户不输入的话,头节点单个节点就是一整个链表(有空头结点,尾节点的指针为NULL)
//使用尾插法,所以命名为 *rear
node *rear = head; //尾插指针rear 首先指向链表最尾端:head;
int temp;
while(scanf("%d",&temp), temp!=0)
{
node *t = malloc(sizeof(node)); //开辟新空间
t->data = temp; //将输入值赋予新空间的data
rear->next = t; //rear 的 next 指针指向新空间
rear = t; //rear 后移到 t 上.
}
rear->next = NULL;
return head; //将头结点指针返回给主函数
}
void printList(node *head)
{
node *cur=head->next;
while(cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
}
int main()
{
node *head; //创建头节点指针
head = creatLink(); //进行创建并初始化链表
printList(head); //输出测试
return 0;
}
测试结果可以.为了方便新手理解,用图来解释一下createLink 的过程吧!
刚开始的
node *head = malloc(sizeof(node)); //1. 申请一块头结点内存.
head->next = NULL; //将头结点的指针先置为NULL,
然后开始新增:
初始化链表已经搞定,开始最简单的查询操作。(限定查询是否在链表中存在某值,存在的话返回这个值所在的节点数.不存在返回-1);
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{//节省空间,不再重复}node;
node *creatLink()
{//节省空间,不再重复}
void printList(node *head)
{//节省空间,不再重复}
int query(node *head)
{
int num_query,i;
printf("输入您要查询的值:\n");
scanf("%d",&num_query);
node *pointer = head->next; //直接跳过传入的空节点指针.
for(i = 1; pointer != NULL; i++)
{
if(pointer->data == num_query)
{
printf("您要查询的值在链表中的第%d个节点\n",i);
return i;
}
pointer = pointer->next;
}
printf("抱歉,没查到.\n");
return -1;
}
int main()
{
node *head; //创建头节点指针
head = creatLink(); //进行创建并初始化链表
//printList(head); //输出测试
query(head); //查询操作.
return 0;
}
插入(传入节点数插入,如传入数值 2 则再第二个节点后插入.)
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
//省略
node *creatLink()
{
//省略
}
void printList(node *head)
{
//省略
}
int query(node *head)
{
int num_query,i;
//省略
}
void insert(node *head)
{
printf("请输入您想要在第___个节点后插入:");
int num, i = 1, newData;
node *temp = head->next;
scanf("%d",&num);
printf("请输入您想要插入的数值");
scanf("%d", &newData);
while(temp != NULL)
{
if(i==num) //得到节点数,在这个节点后进行插入.
{
node *newNode = malloc(sizeof(node));
newNode->data = newData;
newNode->next = temp->next;
temp->next = newNode;
return ;
}
i++;
temp=temp->next;
}
printf("您输入的节点数过大,大于了链表的节点数:%d",i);
/**
试想一下,如果我们不使用头结点,如果我选择 0 节点插入,
即插入在最前面,不仅主函数中的 head 的值要对应改变
(需要将这个insert函数返回值更改为node *)
而且我们要分类去讨论这件事情,如果有了空节点,
第一个节点数据就是从实际上的 第2个节点开始存储,就化为一般问题了.
(尾节点与中间节点相同,不必考虑)
*/
}
int main()
{
node *head; //创建头节点指针
head = creatLink(); //进行创建并初始化链表
insert(head); //插入,
printList(head); //查看链表结果
return 0;
}
开始删除操作。选择想要删除的节点的位置
#include <stdio.h> #include <stdlib.h> typedef struct node { //省略,有兴趣翻阅最上面的完全体代码; void deleteNode(node *head) { int num_delNode, i = 1; printf("请输入您要删除的节点的位置:"); scanf("%d",&num_delNode); node *cur=head->next, *pre = head; while(cur != NULL) { if(i==num_delNode) //得到节点数,对这个节点进行删除. { pre->next = cur->next; free(cur); return ; } i++; pre = cur; cur = cur->next; } printf("您输入的节点数过大,大于了链表的节点数:%d",i); } int main() { node *head; //创建头节点指针 head = creatLink(); //进行创建并初始化链表 //printList(head); //输出测试 //query(head); //查询操作. //insert(head); deleteNode(head); printList(head); return 0; }
最后一个排序操作.
#include <stdio.h>
#include <stdlib.h> //省略 void swap(node *node1, node *node2) { int temp = node1->data; node1->data = node2->data; node2->data = temp; } void linkSort(node* head) { //使用冒泡排序 node *node1,*node2; for(node1 = head->next; node1 != NULL; node1 = node1->next) { for(node2 = node1->next; node2 != NULL; node2 = node2->next ) { if(node1->data > node2->data) { swap(node1,node2); } } } } int main() { node *head; //创建头节点指针 head = creatLink(); //进行创建并初始化链表 linkSort(head); printList(head); return 0; }