一、单链表的特点
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
优点:元素的存储单元是任意的,可连续也可不连续;不需要限定长度。
缺点:其查找时间复杂度为O(n);存放元素时需要另外开辟一个指针域的空间。
二、代码实现
头文件:SList.h
#ifndef SLIST_H_INCLUDED
#define SLIST_H_INCLUDED
#include <stdio.h> //C语言标准库函数:用于输入和输出的函数、类型和宏。声明文件指针的FILE。常用的类型是size_t和fpos_t。
#include <malloc.h> //是动态存储分配函数头文件,当对内存区进行操作时,调用相关函数。
#include <assert.h> //assert函数像if判断语句中一样,然后如果该值为真则正常运行,否则报错,并调用abort(),产生异常中断,exit出来。
#define ElemType int //定义数据元素类型为int
typedef struct Node //定义节点
{
ElemType data; //数据类型
struct Node *next; //Node类型的next指针
}Node,*PNode; //定义了结点的类型Node和指针类型PNode
typedef struct List //定义出单链表的管理方式
{
PNode first; //指向头部
PNode last; //指向尾部
size_t size; //结点个数
}List; //定义单链表的类型List
void InitList(List *list); //初始化链表
void push_back(List *list, ElemType x); //声明尾插函数
void push_front(List *list, ElemType x); //声明头插函数
void show_list(List *list); //声明显示函数
void pop_back(List *list); //声明尾删函数
void pop_front(List *list); //声明头删函数
void insert_val(List *list, ElemType x); //声明按值插入函数
Node* find(List *list,ElemType key); //声明查找函数(返回节点类型)
int length(List *list); //声明长度函数
void delete_val(List *list,ElemType key); //声明按值删除函数
void sort(List *list); //声明排序函数
void reverse(List *list); //声明逆置函数
void clear(List *list); //声明清除函数
void destory(List *list); //声明摧毁函数
#endif // SLIST_H_INCLUDED
实现函数文件:
#include "SList.h"
void InitList(List *list) //初始化函数的实现
{
list->first = list->last = (Node*)malloc(sizeof(Node)); //申请了一个结点,将头和尾都指向该节点
assert(list->first != NULL); //检验结点是否申请成功
list->first->next = NULL; //这里必须要附为空,不然在头插时会乱码
list->size = 0; //此时元素个数为0
}
void push_back(List *list, ElemType x) //尾插函数的实现:从单链表的尾部插入数据
{
Node *s = (Node *)malloc(sizeof(Node)); //申请一个新的结点,令s指向该结点
assert(s != NULL);
s->data = x; //将要插入的值放人该结点的data中
s->next = NULL; //将它的next指针赋空(若不赋空,则是随机值,程序报错)
list->last->next = s;
list->last = s; //将新结点链接进入list的尾部
list->size++; //元素个数加1
}
void push_front(List *list, ElemType x) //头插函数的实现:从单链表的头结点的下一位开始插入节点
{
Node *s = (Node *)malloc(sizeof(Node));
assert(s!= NULL);
s->data = x;
s->next = NULL; //添加新结点,并放入元素
s->next = list->first->next; //这里与尾插不同:在将新结点插入时,必须先让新结点的next指针先指向链表中
list->first->next = s; //再让头指针指向新结点
list->size++; //元素个数加1
}
void show_list(List *list) //显示链表函数的实现
{
Node *p = list->first->next; //创建一个Node类型的指针指向头结点
while(p != NULL) //由于尾结点为空,所以可以在遍历完链表后退出循环
{
printf("%d-->",p->data);
p = p->next;
}
printf("Nul.\n");
}
void pop_back(List *list) //尾删方法
{
if(list->size == 0) //如果链表长度为0,直接返回
{
return;
}
Node *p = list->first;
while(p->next != list->last) //去寻找尾结点的前驱结点
{
p=p->next; //p最终指向尾结点的前驱结点
}
free(list->last); //释放尾结点
list->last = p; //将尾指针指向p(原尾结点的前驱结点)
list->last->next = NULL;
list->size--;
}
void pop_front(List *list) //头删方法的实现
{
if(list->size == 0) //如果链表长度为0,直接返回
{
return;
}
Node *p = list->first->next;
list->first->next = p->next; //头删比尾删简单,但应注意链表中仅有一个元素的情况
free(p);
if(list->size == 1) //如果链表仅有一个元素,删除后,其尾首指针都应指向头结点
{
list->last = list->first;
}
list->size--;
}
void insert_val(List *list,ElemType x) //按值插入函数的实现:前提是在链表有序的条件下按值插入
{
Node *s = (Node *)malloc(sizeof(Node)); //创建一个新结点
assert(s!= NULL);
s->data = x; //这里已经将要插入的值放入新结点中
s->next = NULL;
Node *p = list->first;
while(p->next!=NULL && p->next->data<x) //创建了新指针p去寻找s结点该插入的位置
{
p = p->next;
}
if(p->next == NULL) //在尾部插入的情况
{
list->last = s;
}
s->next = p->next;
p->next = s;
list->size++;
}
Node* find(List *list,ElemType x) //查询函数的实现:函数类型为指针类型,说明返回的是一个指针
{
Node *p = list->first->next;
while(p!=NULL && p->data!=x) //遍历有没有等于x值的结点
{
p = p->next;
}
return p;
}
int length(List *list) //链表长度函数的实现
{
return list->size;
}
void delete_val(List *list,ElemType key) //按值删除函数的实现
{
if(list->size == 0) //为空直接返回
{
return;
}
Node *p = find(list,key); //查找该元素是否存在,存在则p指向该结点
if(p == NULL)
{
printf("要删除的数据不存在。");
return;
}
if(p == list->last) //如果该值在链表尾部,则用尾删法
{
pop_back(list);
}
else //若在中间
{
Node *q = p->next; //用q指向要删除结点的下一结点
p->data = q->data; //将下一结点的值赋给当前要删除结点的data
p->next = q->next; //将p所指的结点next跨过下一结点
free(q); //释放q结点
list->size--;
}
}
void sort(List *list) //排序函数的实现:将链表从第一个结点后拆分成两个链表,再不断从后一链表中取值进来排序
{
if(list->size==0 || list->size==1)
{
return;
}
Node *s = list->first->next; //s指向第一个结点
Node *q = s->next; //q只想第二个结点
list->last = s;
list->last->next = NULL; //从第一个结点后开始拆分(这里s指针就空出来了)
while(q != NULL)
{
s = q; //将s指针指向q所指链表的第一个结点
q = q->next; //q指向链表的下一元素
Node *p = list->first; //创建p指针去对新插入的结点排序
while(p->next!=NULL && p->next->data<s->data) //遍历链表寻找插入位置
{
p = p->next;
}
if(p->next == NULL)
{
list->last = s;
}
s->next = p->next;
p->next = s;
}
}
void reverse(List *list) //逆置函数的实现:同理也是拆分成两个链表,不断进行头插达到逆置的效果
{
if(list->size == 0 || list->size == 1)
{
return;
}
Node *p = list->first->next;
Node *q = p->next;
list->last = p;
list->last->next = NULL; //拆分成两个链表(排序函数相同)
while(q != NULL)
{
p = q; //p指向q的第一个结点
q = p->next; //q滑向下一结点
p->next = list->first->next;
list->first->next = p; //头插结点(必须要让要插入的结点先链入表中)
}
}
void clear(List *list) //链表清除函数的实现
{
if(list->size == 0)
{
return;
}
Node *p = list->first->next; //p始终指向头结点的下一结点
while(p != NULL) //遍历元素,如果
{
list->first->next = p->next; //若到达最后一个元素,则其p->next为空则可退出循环
free(p); //释放p所指向的结点
p = list->first->next;
}
list->last = list->first;
list->size = 0;
}
void destory(List *list) //摧毁链表函数的实现
{
clear(list); //先将链表清除
free(list->first); //再将头结点释放
list->first = list->last = NULL;
list->size = 0;
}
主函数:
#include "SList.h"
int main()
{
List mylist; //创建一个单链表
InitList(&mylist); //初始化单链表
ElemType Item; //定义一个int类型的数
Node *p = NULL; //定义一个Node类型的指针
int select = 1;
while(select)
{
printf("**********************************************\n");
printf("*[1] push_back [2] push_fornt *\n"); //尾插 头插
printf("*[3] show_list [4] pop_back *\n"); //显示 尾删
printf("*[5] pop_list [6] insert_val *\n"); //头删 按值插入
printf("*[7] find [8] length *\n"); //查找 链长
printf("*[9] delete_val [10] sort *\n"); //按数值删 排序
printf("*[11] resver [12] clear *\n"); //逆置 清除
printf("*[13] destroy [0] qiut_system *\n"); //摧毁 退出
printf("**********************************************\n");
printf("请选择:>");
scanf("%d",&select);
if(select == 0)
{
break;
}
switch (select) //根据select值选择不同功能
{
case 1: //尾插
printf("请输入要插入的数据(-1结束):>");
while(scanf("%d",&Item),Item != -1)
{
push_back(&mylist,Item);
}
break;
case 2: //头插
printf("请输入要插入的数据(-1结束):>");
while(scanf("%d",&Item),Item != -1)
{
push_front(&mylist,Item);
}
break;
case 3: //显示
show_list(&mylist);
break;
case 4: //尾删
pop_back(&mylist);
break;
case 5: //头删
pop_front(&mylist);
break;
case 6: //按值插入
printf("请输入要插入的数据:>");
scanf("%d",&Item);
insert_val(&mylist,Item);
break;
case 7: //查询
printf("请输入要查找的数据:>");
scanf("%d",&Item);
p = find(&mylist,Item);
if(p == NULL)
{
printf("要查找的数据不存在。\n");
}
else
{
printf("查找成功,数据存在。\n");
}
break;
case 8: //链表长度
Item = length(&mylist);
printf("链表当前长度为:>%d\n",Item);
break;
case 9: //按值删除
printf("请输入要删除的值:>");
scanf("%d",&Item);
delete_val(&mylist,Item);
break;
case 10: //排序
sort(&mylist);
break;
case 11: //逆置
reverse(&mylist);
break;
case 12: //清除
clear(&mylist);
printf("链表清除成功,请重新输入数据。\n");
break;
case 13: //摧毁
destory(&mylist);
printf("链表摧毁成功,已自动退出程序。\n");
exit(0);
break;
case 0: //退出
break;
default:
printf("输入命令错误,请重新输入。\n");
break;
}
}
destory(&mylist);
return 0;
}
/**
* 本次编程所得:
* 1.要考虑边界条件和特殊情况(最容易出bug的地方)。
* 2.牵扯到链表的操作,很少直接对链表中的数据直接进行操作,而是对链表整体或结构进行操作。
*/
三、说明
编写代码的IDE是CodeBlock。
代码百度网盘链接:
链接:https://pan.baidu.com/s/1opVhpl5Bbh8GK-aJN-dqeQ
提取码:x0q9