前言
以下代码来自于郝斌老师数据结构视频,加以自己的理解作了注释,用于学习和复习,欢迎指正错误。
代码的运行环境:DEV-C++ 6.3版本,创建源文件为.cpp文件即可编译运行。
概念
单链表又称线性表的链式存储,即通过一组任意的存储单元来存储线性表中的数据元素。为了建立数据元素之间的线性关系,对每个链表节点,除存放元素之神的信息外,还需要存放一个指向后继结点的指针。每个节点包含一个data和一个next指针。
头指针:链表中第一个结点的存储位置叫做头指针,它指向头结点的数据域。头指针就是链表的身份和名字,是链表的必要元素。
头结点:为了操作方便,在单链表的第一个结点之前插入一个结点,就是头结点。头结点的数据域可以不设任何信息,也可以存储链表的长度等。头结点的指针域指向链表的第一个元素结点。
头结点的优点:1、由于第一个数据结点的位置被存放在头结点的指针域中,因此在链表的第一个数据结点位置上的操作和和在其他位置上的操作一致。无需特殊处理。2、无论链表是否为空,其头指针都指向头结点的非空指针,(空表中头结点的指针域为空)因此空表和非空表的处理得到了统一。
首结点:或称首元结点,单链表中第一个存放数据元素的结点。
#include <stdio.h>
#include <stdlib.h>
typedef struct Node{
int data; //链表的数据域
struct Node *pNext; //链表的指针域
}NODE,*PNODE; //NODE等价于struct Node,PNODE等价于struct Node *
PNODE creat_list(void); //创建链表
void traverse_list(PNODE pHead); //遍历打印链表
bool is_empty(PNODE pHead); //判断链表是否空
int length_list(PNODE pHead); //返回链表长度
bool insert_list(PNODE pHead,int pos,int val); //插入
bool delete_list(PNODE pHead,int pos,int *pVal);//删除
void sort_list(PNODE pHead); //链表排序
int main(void){
int len=0;
int val;
PNODE pHead = NULL; //等价于struct Node *pHead = NULL
pHead = creat_list(); //创建一个非循环单链表,将该链表的头结点的地址赋给pHead
traverse_list(pHead); //遍历打印链表
insert_list(pHead,4,12);
traverse_list(pHead); //遍历打印链表
if(delete_list(pHead,4,&val)){
printf("Delete successfully!\n");
printf("The value is %d\n",val);
}
else{
printf("Delete failed!\n");
}
traverse_list(pHead);//遍历
// if(is_empty(pHead)){
// printf("The list is empty!\n");
// }
// len = length_list(pHead);
// printf("%d\n",len);
sort_list(pHead);
traverse_list(pHead);//遍历
}
/*------------------------------
函数功能:尾插法创建单链表
参数为空
返回值为头结点地址
函数类型为PNODE
------------------------------*/
PNODE creat_list(void){
int len;
int i;
int val;//临时存放用户输入的结点的值
//分配了一个不存放有效数据的头结点
/*-------------------------------
头结点:第一个有效结点之前的结点,
而第一个有效结点被称为首结点
------------------------------*/
/*
动态分配内存的函数开辟成功会返回开辟空间的地址,失败则返回NULL
*/
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(NULL == pHead){ //所以此处判断NULL是否成功分配内存
printf("Memory allocation Failed!\n");
exit(-1);
}
PNODE pTail = pHead; //pTail指向头结点和pHead一样
pTail->pNext = NULL; //把pTail的指针域pNext清空,也就是指向尾结点
printf("Please input the list number you want to creat:\nlen= ");
scanf("%d",&len);
for(i=0;i<len;i++){
printf("Please enter the value of %dth node:",i+1);
scanf("%d",&val);
PNODE pNew = (PNODE)malloc(sizeof(NODE));//创建pNew,临时结点
if(NULL == pHead){
printf("Memory allocation Failed!\n");
exit(-1);
}
pNew->data = val; //将用户数据赋给临时结点pNew的数据域
pTail->pNext = pNew; //pTail的指针域指向pNew,获取pNew的地址
pNew->pNext = NULL; //pNew的指针域清空,变成尾结点
pTail = pNew; //pTail变成尾结点
/*这样的效果就是每次新加的结点pTail自动成为尾结点*/
}
return pHead;
}
/*------------------------------
函数功能:遍历链表
参数:pHead---接收链表的头结点地址
函数类型为PNODE
------------------------------*/
void traverse_list(PNODE pHead){
//空链表仍然会有一个结点,就是头结点
PNODE p = pHead->pNext;//头结点指向首结点,如果指向NULL,说明链表为空
//p为NULL时,说明链表是空的,不为NULL时,就是有结点
while(NULL != p){
printf("%d ",p->data);
p = p->pNext;//注意链表不是顺序存储,不可以用p++来遍历
}
printf("\n");
return;
}
/*----------------------------------
函数功能:判断链表是否为空
参数:pHead---链表的头结点
为空返回true,不为空的话返回false
-----------------------------------*/
bool is_empty(PNODE pHead){
if(NULL == pHead->pNext){//如果为空的话,头结点的指针域指向尾结点的NULL
return true;
}
else{
return false;
}
}
/*----------------------------------
函数功能:获得链表的长度并返回
参数:pHead---链表的头结点
-----------------------------------*/
int length_list(PNODE pHead){
//创建一个p结点,令其指向头结点所指的结点,也就是首结点
PNODE p = pHead->pNext;
int len = 0;
while(NULL!=p){
++len;
p=p->pNext;//移向后一个结点
}
return len;
}
/*----------------------------------
函数功能:向链表中插入数据
参数:pHead---链表的头结点
pos---插入的位置
val---插入的数据
-----------------------------------*/
bool insert_list(PNODE pHead,int pos,int val){
//int val=0;
int i=0;
PNODE p=pHead;
//首先判断链表是否为空以及插入位置是否合法
//满足一个条件就返回false,终止程序
if(i>pos-1||NULL==p->pNext){
return false;
}
//让p移到插入结点的前一个结点
while(NULL != p && i<pos-1){
p=p->pNext;
++i;
}
//一个新的临时结点,动态分配内存
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew){
printf("动态内存分配失败!\n");
exit(-1);
}
pNew->data = val;
PNODE q=p->pNext;
p->pNext = pNew;
pNew->pNext = q;
return true;
}
/*----------------------------------
函数功能:从链表中删除数据
参数:pHead---链表的头结点
pos---删除数据的位置
*pVal---返回删除的数据
-----------------------------------*/
bool delete_list(PNODE pHead,int pos,int *pVal){
int i=0;
PNODE p = pHead;
//如果输入的位置不合法或者链表为空,返回false
if(i>pos-1||NULL==p->pNext){
return false;
}
//如果p->pNext=NULL说明已经到链表尾结点,应该退出循环
//i<pos-1,例如要删除第5个结点,i<4也就是循环到3退出
//因为i是从0开始的,循环到3也就是循环了4次
//两者中有一个不满足条件就会退出循环
while(NULL!=p->pNext&&i<pos-1){
p = p->pNext;
++i;
}//退出循环的p是第四结点
PNODE q = p->pNext;//此时的q是第五个结点,因为p->pNext是第四个结点的指针域,它指向的是第五个结点
*pVal = q->data;//将第五个结点的数据传出
//删除p结点后面的结点
//令第四个结点的指针域变成第五个结点的指针域
//或者说原本第四结点指向第五结点,现在指向第六结点
p->pNext = p->pNext->pNext;
//第五结点变成了q,所以需要释放第五结点的内存
//动态分配的内存必须使用free释放
free(q);
q=NULL;//断开q和该地址的联系
return true;
}
/*----------------------------------
函数功能:对链表数据进行排序
参数:pHead---链表的头结点
-----------------------------------*/
void sort_list(PNODE pHead){
int i,j,t;
int len;
len = length_list(pHead);
PNODE p,q;
for(i=0,p=pHead->pNext;i<len-1;i++,p=p->pNext){
//p=pHead->pNext相当于对p初始化,使其指向该链表的首结点
//p=p->pNext,其指针域指向下一个结点,每循环一次p都会更新为下一个结点
for(j=i+1,q=p->pNext;j<len;j++,q=q->pNext){
/*p->pNext是首结点后的第二结点,也就是q变成第二结点
q=q->pNext自增到链表的尾结点,<len在len-1处停下来
因为i是从0开始的,循环次数和链表结点个数对应即可
类似于数组a[i]>a[j]*/
if(p->data>q->data){ //a[i]>a[j]
t = p->data; //t = a[i]
p->data = q->data; //a[i] = a[j]
q->data = t; //a[j] = t
}
}
}
}