单链表是数据结构中最基本的数据结构,单链表有头插法和尾插法,今天有空把这两者做成一个实验示例以作比较。
提示:编译代码是否通过可能与编译器的版本有关,本程序是在 Android 操作系统下的 Compiler C语言编译器下编译通过。
一、头插法和尾插法创建单链表
增、删、改、查的算法只写了增、查两个,其它省略。
/*程序功能:单链表的头插法和尾插法实验示例说明:本程序的增删改查功能部分实现作者:九天一声啸邮箱:946585434@qq.com日期:2022.09.09
*/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
#include<assert.h>#define BOOL int
#define TRUE 1
#define OK 1
#define FALSE 0
#define NO 0
#define ERROR -1//链表长度int length = 16;//TRUE 为自然数列,FALSE 为随机数
bool choose = true;
//bool choose = false;//定义结构体
typedef struct Node
{int data;struct Node *next;}Node, *LinkList;LinkList head = NULL;
LinkList end = NULL; //尾插法时使用的结构体指针//头插法函数
void addHead(int data)
{LinkList L;L = (LinkList)malloc(sizeof(Node));L->data = data;L->next = head->next;head->next = L;}//尾插法函数
void addEnd(int data) {LinkList L;L = (LinkList)malloc(sizeof(Node));L->next = NULL;L->data = data;if(NULL == head){head = L;} else {end->next = L;}end = L;}//头插法遍历链表并输出
void showListHead()
{LinkList tmp;tmp = head->next;while (tmp != NULL){printf ("%d ", tmp->data);tmp = tmp->next;}}//尾插法遍历链表并输出
void showListEnd()
{LinkList tmp;tmp = head;while(tmp){printf("%d ", tmp->data);tmp = tmp->next;}}int main()
{int i;char str[10];BOOL tag;srand(time(0));printf("请输入内容,true 是尾插法;false 是头插法:\n\t->");scanf("%s",&str);/*判断输入内容tag = 1 为true;tag = 0 为falsetag = -1 为error*/if(strcmp(str, "true") == 0)tag = TRUE;else if(strcmp(str, "false") == 0)tag = FALSE;elsetag = ERROR;if(tag == TRUE) {//头插法需要这段代码,否则错误head = (LinkList)malloc(sizeof(Node));head->next = NULL;//头插法插入数据for(i = 0; i < length; i++){if(choose == true)addHead(i);else//随机数addHead(rand()%100 + 1);}printf("头插法:\n");//头插法显示showListHead();} else if(tag == FALSE) {//尾插法插入数据for(i = 0; i < length; i++){if(choose == true)addEnd(i);else//随机数addEnd(rand()%100 + 1);}printf("尾插法:\n");//尾插法显示showListEnd();} else {printf("输入错误!");}return 0;}
程序执行后的效果:
二、完整的单链表数据结构如下:
在实际应用中,结构体中 data 的数据类型应该是另一个结构体数据类型,即 data 代表数据元素,用于定义姓名、姓别、年龄、地址等等数据项。
/*程序功能:单链表的头插法和尾插法实验示例说明:本程序的增删改查功能全部实现作者:九天一声啸邮箱:946585434@qq.com日期:2022.09.09
*/
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
#include<assert.h>//自定义布尔类型
#define BOOL int
#define TRUE 1
#define OK 1
#define FALSE 0
#define ERROR -1BOOL tag;//自定义数据类型 ElemType
#define ElemType int//链表长度
int length = 16;/*true 为自然数列,false 为随机数
*/
//bool choose = true;
bool choose = false;//定义结构体
typedef struct Node{/*data 代表的是数据元素,实际应用中 data 应该是另一个结构体,并且在结构体中定义数据项。*/int data; //结点数据struct Node *next; //结点指针
}Node, *LinkList;LinkList head = NULL;
LinkList end = NULL; //尾插法时使用的结构体指针/*功能:头插法算法函数@param int data 链表数据@return void
*/
void addHead(int data){LinkList L;//头插法核心代码 beginif(NULL == head){//如果没有头节点,创建头节点head = (LinkList)malloc(sizeof(Node));head->next = NULL;}else{L = (LinkList)malloc(sizeof(Node));L->data = data;L->next = head->next;head->next = L;}//头插法核心代码 end.
}/*功能:尾插法算法函数@param int data 链表数据@return void
*/
void addEnd(ElemType data){LinkList L;//尾插法核心代码 beginL = (LinkList)malloc(sizeof(Node));L->data = data;L->next = NULL;if(NULL == head){//如果没有头节点,创建头节点head = (LinkList)malloc(sizeof(Node));head->next = L;}else{end->next = L;}end = L;//尾插法核心代码 end.
}/*功能:返回头结点指针@return LinkList 自定义结构体指针类型
*/
LinkList getLinkNode()
{LinkList L;L = head->next;return L;
}/*功能:根据输入链表的位置查询链表数据@param int num 待查询链表的位置@return int
*/
int getElem(int num)
{LinkList L;int i;/*由于程序是从 0 开始计数的,见 linkCreate() 函数内容,我们数数时是从 1 开始的,为了让认知一致,i 应该从 1 开始计数。*/i = 1;L = getLinkNode();//循环移动指针到指定位置while(L && i < num){//指针向后移动一位L = L->next;++i;}if(!L || i > num)return ERROR;//查询节点数据的核心代码,直接返回数据即可return L->data;
}/*功能:根据数据位置修改链表@param int num 待查询链表的位置@param int data 待修改的数据@return BOOL 自定义的布尔类型
*/
BOOL changeElem(int num, ElemType data)
{LinkList L;int i;i = 1;L = getLinkNode();//循环移动指针到指定位置while(L && i < num){//指针向后移动一位L = L->next;++i;}if(!L || i > num)return ERROR;//修改节点数据的核心代码 beginL->data = data;//修改节点数据的核心代码 endreturn OK;
}/*功能:根据输入链表的位置插入链表节点@param int num 待插入链表的位置@param int data 待插入的数据@return BOOL 自定义的布尔类型
*/
BOOL insertList(int num, ElemType data)
{LinkList L, s;int j;j = 1;L = getLinkNode();//循环移动指针到指定位置while(L && j < num){//指针向后移动一个节点L = L->next;++j;}if(!L || j > num)return ERROR;//插入节点和数据的核心代码 begins = (LinkList)malloc(sizeof(Node));s->data = data;s->next = L->next;L->next = s;//插入节点和数据的核心代码 endreturn OK;
}/*功能:根据输入链表的位置删除链表的节点@param int num 要删除链表的位置@return ElemType 自定义类型
*/
ElemType deleteList(int num)
{LinkList L, s;ElemType tmp;int j;/*由于程序是从0开始计数的,见 linkCreate() 函数内容,我们数数时是从1开始的,为了让认知一致,j 应该从 1 开始计数;再由于,要删除的数据是从查询到的位置的下一个位置开始删的,所以这里 j 再加1,即 j = 2。*/j = 2;/*双向链表有前驱指针可以在删除第一个节点时,直接指向头节点。然而,由于单向链表没有前驱指针,在指向头节点后的第一个节点时,在删除第一个节点时删除不了第一个节点,还是删除的是第二个节点,因此这里如果要删除第一个节点的话,要把指针指向头节点。*/if (num == 1)/*删除第一个节点时,指针指向头节点,头节点没有数据内容,即 head->data 为空。*/L = head;else/*删除其它节点时,指针指向头节点后有数据内容的第一个节点,即 head->next->data 是 head 头节点后,第一个节点的数据。注意:head->next 是 head 节点后的第一个节点。*/L = getLinkNode();//循环移动指针到指定位置while(L && j < num){//指针向后移动一个节点L = L->next;j++;}if((!L || j > num) && num != 1)return ERROR;//删除节点的核心代码 begins = L->next ;L->next = s->next ;tmp = s->data;free(s);//删除节点的核心代码 endreturn tmp;
}/*功能:链表的整表删除@return BOOL 自定义布尔类型
*/
BOOL allDeletList()
{LinkList L, s;s = head->next;//整表删除的核心代码 beginwhile(s){L = s->next;free(s);s =L;}//整表删除的核心代码 end//TRUE:头插法;FALSE:尾插法if(tag == TRUE)head->next = NULL;elsehead = NULL;return OK;
}//----------- 以上是单链表的算法实现
//----------- 以下是辅助函数和程序,不必深究。/*功能:头插法遍历链表并输出@return void
*/
void showListHead()
{LinkList tmp;tmp = head->next;while (tmp != NULL){printf ("%d ", tmp->data);tmp = tmp->next;}
}/*功能:尾插法遍历链表并输出@return void
*/
void showListEnd()
{LinkList tmp;bool flag = false ;tmp = head;while(tmp != NULL){if (flag)printf("%d ", tmp->data);else flag = true;tmp = tmp->next;}
}/*功能:遍历单链表并显示@return void
*/
void showList()
{//TRUE:头插法;FALSE:尾插法if(tag == TRUE){//头插法遍历显示showListHead();}else if(tag == FALSE){//尾插法遍历显示showListEnd();}
}/*功能:创建单链表@return BOOL 自定义的布尔类型
*/
BOOL linkCreate()
{int i;char str[10];srand(time(0));printf("1.请输入内容,true 是头插法;false 是尾插法:\n\t-> ");scanf("%s",&str);/*判断输入内容tag = 1 为true;tag = 0 为falsetag = -1 为error*/if(strcmp(str, "true") == 0)tag = TRUE;else if(strcmp(str, "false") == 0)tag = FALSE;elsetag = ERROR;//TRUE:头插法;FALSE:尾插法;ERROR:错误if(tag == TRUE){//头插法插入数据for(i = 0; i < length; ++i){if(choose == TRUE)addHead(i);else//随机数addHead(rand()%100 + 1);}printf("头插法:\n");//头插法遍历显示showListHead();return OK;}else if(tag == FALSE){//尾插法插入数据for(i = 0; i < length; i++){if(choose == TRUE)addEnd(i);else//随机数addEnd(rand()%100 + 1);}printf("尾插法:\n");//尾插法遍历显示showListEnd();return OK;}else{printf("输入错误!");return ERROR;}}/*功能:根据输入链表的位置查询数据@return void
*/
void linkQuery()
{int input;printf("\n\n2.请输入要查询的位置:\n\t-> ");scanf("%d", &input);if(input <= length){int getNum = getElem(input);printf("\n获取到的值:%d", getNum);}else{printf("\n╳ 没有这么长的链表!");}
}/*功能:根据输入链表的位置修改数据@return void
*/
void linkRevise()
{int input, data;printf("\n\n3.请输入要修改的位置和数据,两个数据用空格隔开:\n\t-> ");scanf("%d%d", &input, &data);if(input <= length){changeElem(input, data);printf("\n链表修改后的值:\n");showList();}else{printf("\n╳ 没有这么长的链表!");}
}/*功能:根据输入链表的位置插入数据@return void
*/
void linkInsert()
{int input, data;printf("\n\n4.请输入要插入的位置和数据,两个数据用空格隔开:\n\t-> ");scanf("%d%d", &input, &data);if(input <= length){int ret = insertList(input, data);if(ret == OK)length++;printf("\n链表修改后的值:\n");showList();}else{printf("\n╳ 没有这么长的链表!");}
}/*功能:根据输入链表的位置删除数据@return void
*/
void linkDelete()
{int input, data, tmp;printf("\n\n5.请输入要删除的位置:\n\t-> ");scanf("%d", &input);if(input <= length){tmp = deleteList(input);if(tmp != ERROR)length--;printf("\n->被删除的值:%d\n\n", tmp);printf("\n链表删除后的值:\n");showList();}else{printf("\n╳ 没有这么长的链表!");}
}/*功能:主函数入口@return int
*/
int main()
{//链表操作:创建链表BOOL val = linkCreate();if(val != ERROR){printf("\n--------------------------------\n");//链表查询linkQuery();printf("\n--------------------------------\n");//链表修改linkRevise();printf("\n--------------------------------\n");//链表插入linkInsert();printf("\n--------------------------------\n");//链表删除linkDelete();printf("\n--------------------------------\n");//整表删除allDeletList();printf("\n6.整表删除后的链表数据:");showList();}return 0;
}
程序执行后的效果:
三、结构体定义的完整的数据结构演示如下:
把结构体的数据域写全,并用最原始的方法来演示一下单链表。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdbool.h>//结构体定义的数据域
typedef struct myData{int id;char name[20];short int sex; //0为男;1为女char address[100];
}myData;//结构体定义的节点
typedef struct Node{//数据域,这里用结构体变量,不能直接用指针,//如果没有变量,指针就不能指向变量的地址。struct myData data;//指针域struct Node *next;
}Node, *LinkList;//性别判断
void determine(short int flag, char* e)
{if(flag == 0)strcpy(e, "男");else if(flag == 1)strcpy(e, "女");elsestrcpy(e, "未知");
}//主函数入口
int main()
{char ssex[10] ="未知";//1.结构体变量,静态分配内存Node link1; //赋值link1.data.id = 12;strcpy(link1.data.name, "孙悟空");link1.data.sex = 0;strcpy(link1.data.address, "花果山水帘洞");//性别判断determine(link1.data.sex, ssex);printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", link1.data.id, link1.data.name, ssex, link1.data.address);//2.malloc() 函数动态分配内存LinkList link2 = (LinkList)malloc(sizeof(Node));//赋值link2->data.id = 13;strcpy(link2->data.name, "嫦娥");link2->data.sex = 1;strcpy(link2->data.address, "月亮广寒宫");//性别判断determine(link2->data.sex, ssex);printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", link2->data.id, link2->data.name, ssex, link2->data.address);//3.如果把上面的两个结构体变量组合成一个单向链表/*用这种最原始的方法,才能看清楚它们的关系这里 link1 是结构体变量,而 link2 是 malloc() 直接返回的就是指针,非常方便。*/Node L; //内存分配一个头节点Node* head = &L;//这里等效于:Node* head = (Node*)malloc(sizeof(Node));head->next = &link1;head->next->next = link2;head->next->next->next = NULL; //单链表要求尾节点的 next 为空/*单链表生成完毕,这是最直观的单链表了,上面的五段代码看明白了,单链表的算法已就能写出来了。说明:1.一般要求头节点除了 next 指针域是有效数据,data 数据域是无效数据,不用管它;2.也可以在头节点放置链表长度,这时头节点要单独定义一个结构体,指针域不变,数据域为:int length; 只需把 next 指向其它有数据的节点即可。这样的好处就是在获取单链表的长度时,不用遍历整个链表,也就是时间复杂度由 O(n) 变为了 O(1),但是,也可以不需要头节点。3.一个 next 其实就是一个有数据内容的节点,而最后的尾节点为 NULL 而已。*/printf("以下内容是从单链表输出的:\n\n");//显示第一个有数据的节点内容determine(head->next->data.sex, ssex);printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", head->next->data.id, head->next->data.name, ssex, head->next->data.address);//显示第二个有数据的节点内容determine(head->next->next->data.sex, ssex);printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n", head->next->next->data.id, head->next->next->data.name, ssex, head->next->next->data.address);return 0;
}
程序执行后的效果: