线性表的链式表示即通过链表的形式实现线性结构。
我们可以用C语言可以定义一个结构体,结构体可以分为两部分,一部分存储想要进行保存的数据,另一部分用来定义一个指向该结构体的指针。此指针指向下一个这样的结构体,下个结构体的指针又可以指向下下一个结构体…
如此往复,结构体直接互相连接,从而可以实现链表的形式。
对于上一次的以数组存储的以顺序形式实现的线性结构,逻辑地址相邻,物理地址也相邻,具有方便随机存取查看,不便插入删除(都要前移元素或后挪)。
这次的以链表形式实现的线性结构,逻辑地址相邻,物理地址不一定相邻,具有方便插入删除(无需向前后移动数据),不便随机存取查看(需要进行遍历)。
链表形式线性表的所有算法思想操作如下:
结构体链表定义
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//定义结构体Student,作为链表的数据
struct Student{
int id;
char *name;
int age;
};
//定义链表结构体(此结构体也可单独作为链表结点)
typedef struct LNode{
Student student; //数据Student
LNode *next; //下一结点LNode
}LNode, *LinkList; //LinkList即为链表的定义形式
所有函数声明
int initLinkList(LinkList &list); //初始化链表
void printLinkList(LinkList &list); //打印链表列表
int getStudentList(LinkList list, int i, Student &student); //获取链表中某一元素
int insertLinkList(LinkList &list, int i, Student student); //为链表的指定位置插入元素
int deleteLinkList(LinkList &list, int i, Student &student); //删除链表指定位置的元素
int mergeLinkList(LinkList &list_a, LinkList &list_b, LinkList &list_c); //合并有序链表list_a、list_b为新有序单链表list_c
初始化链表
//链表的初始化操作
int initLinkList(LinkList &list)
{
list = (LNode *)malloc(sizeof(LNode)); //开辟头结点指针
list->next = NULL; //头结点的后继结点初始化为NULL
return OK; //返回成功信息
}
获取链表某一元素
//数据结构 算法2.8---->获取线性表中的某个元素
int getStudentList(LinkList list, int i, Student &student)
{
if(i < 1) return ERROR; //若序号小于1表示获取元素失败,返回错误信息
int j = 1; //定义标识位j并设置为1
LNode *p = list; //定义p指针指向链表的头结点
//循环找出链表中的第i个结点,令p指向它
while(j <= i)
{
p = p->next; //指针后挪,遍历下一结点
if(p==NULL) return ERROR; //若结点已为NULL,返回错误信息
j++; //计数器累加
}
student = p->student; //将该结点的信息赋值返回
return OK; //返回操作成功信息
}
链表插入操作
//数据结构 算法2.9---->指定位置插入一个元素
int insertLinkList(LinkList &list, int i, Student student)
{
int j = 1;
if(i < 1) return ERROR; //若插入位置小于1,返回ERROR失败信息,插入失败
LNode *p = list, *node; //定义p指针指向头节点,定义node结点作为插入结点
//通过循环找到待插入结点的前一个结点,即i-1个结点
for(j = 1; j < i && (p!=NULL); j++)
{
p = p->next;
}
if(p == NULL && j >= i) return ERROR; //若前一个结点为NULL或者插入位置超出范围表示插入失败,返回失败信息
node = (LNode *)malloc(sizeof(LNode)); //开辟一个新的结点内存信息
node->student = student; //保存插入元素的值
node->next = p->next; //将目前第i个位置的结点作为node结点的后继结点
p->next = node; //再将第i-1个结点的后继结点作为node结点,即插入第i个位置成功
return OK; //返回插入成功信息
}
链表删除操作
//数据结构 算法2.10---->指定位置删除一个元素,并返回该元素的值
int deleteLinkList(LinkList &list, int i, Student &student)
{
int j = 1; //定义统计次数标识位j并初始化为1
if(i < 1) return ERROR; //如果删除位置小于1,则返回删除失败
LNode *p = list, *q; //定义p指针最先指向链表头指针
//循环遍历找到链表的第i-1个位置,准备进行插入
for(j = 1; j < i && (p->next!=NULL); j++)
{
p = p->next;
}
//如果第i个位置为null或者删除位置已超出,返回删除失败信息
if(p->next == NULL || j >= i) return ERROR;
student = p->next->student; //将待删除位置的结点信息赋值为student,待传回操作
q = p->next; //保存删除结点的位置信息
p->next = p->next->next; //直接将待删除结点的前一个结点的后继结点 连接到 待删除结点的后继节点(相当于删除)
free(q); //释放删除结点的地址信息数据
return OK; //返回删除成功
}
合并两个有序单链表操作
//数据结构 算法2.12---->合并两个有序单链表
int mergeLinkList(LinkList &list_a, LinkList &list_b, LinkList &list_c)
{
LNode *pa = list_a->next, *pb = list_b->next, *pc; //定义pa、pb、pc三个指针, pa、pb分别指向链表list_a, list_b
initLinkList(list_c); //初始化链表c
pc = list_c; //pc指向链表list_c
//若链表pa,pb都未合并完毕,重复循环插入
while(pa != NULL && pb != NULL)
{
if(pa->student.id <= pb->student.id) //按照升序进行有序合并,若pa指向结点学生的序号小于pb
{
pc->next = pa; //则把pa指向结点添加到pc指向链表之后
pc = pa; //pc移动到后一个结点(即pa),准备添加下一个结点
pa = pa->next; //pa添加过之后,指针也向后进行移动
}
else //反之亦然
{
pc->next = pb;
pc = pa;
pb = pb->next;
}
}
if(pa != NULL) pc->next = pa; //若最终pa指向链表未添加完毕,直接将pa剩余链表接到pc链表后
if(pb != NULL) pc->next = pb; //若最终pb指向链表未添加完毕,直接将pb剩余链表接到pc链表后
return OK; //返回成功信息
}
打印单链表操作
//打印所有结点
void printLinkList(LinkList &list)
{
int i = 0; //设置i作为记录结点个数
LNode *p = list->next;
while(p!=NULL) //循环输出所有结点
{
printf("id = %d.\tname = %s.\tage = %d.\n", p->student.id, p->student.name, p->student.age); //输出结点信息
p = p->next; //p指针后挪至下一结点信息
i++;
}
printf("输出链表列表完毕,共%d条数据.\n", i);
}
合并代码+测试
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//定义结构体Student,作为链表的数据
struct Student{
int id;
char *name;
int age;
};
//定义链表结构体(此结构体也可单独作为链表结点)
typedef struct LNode{
Student student; //数据Student
LNode *next; //下一结点LNode
}LNode, *LinkList;
int initLinkList(LinkList &list); //初始化链表
void printLinkList(LinkList &list); //打印链表列表
int getStudentList(LinkList list, int i, Student &student); //获取链表中某一元素
int insertLinkList(LinkList &list, int i, Student student); //为链表的指定位置插入元素
int deleteLinkList(LinkList &list, int i, Student &student); //删除链表指定位置的元素
int mergeLinkList(LinkList &list_a, LinkList &list_b, LinkList &list_c); //合并有序链表list_a、list_b为新有序单链表list_c
int main()
{
LinkList list;
initLinkList(list);
LNode *q = list, *p = list;
for(int i = 0; i < 10; i++)
{
p = (LNode *)malloc(sizeof(LNode));
p->student.id = i;
p->student.name = {
"Hello World"};
p->student.age = i+20;
q->next = p;
q = p;
}
q->next = NULL;
printLinkList(list);
while(1)
{
int i = 0;
Student student;
printf("请输入您想要查找的元素:\n");
scanf("%d", &i);
if(getStudentList(list, i , student))
{
printf("查找成功!\n");
printLinkList(list);
}
else printf("查找失败!\n");
}
return 0;
}
//链表的初始化操作
int initLinkList(LinkList &list)
{
list = (LNode *)malloc(sizeof(LNode)); //开辟头结点指针
list->next = NULL; //头结点的后继结点初始化为NULL
return OK; //返回成功信息
}
//数据结构 算法2.8---->获取线性表中的某个元素
int getStudentList(LinkList list, int i, Student &student)
{
if(i < 1) return ERROR; //若序号小于1表示获取元素失败,返回错误信息
int j = 1; //定义标识位j并设置为1
LNode *p = list; //定义p指针指向链表的头结点
//循环找出链表中的第i个结点,令p指向它
while(j <= i)
{
p = p->next; //指针后挪,遍历下一结点
if(p==NULL) return ERROR; //若结点已为NULL,返回错误信息
j++; //计数器累加
}
student = p->student; //将该结点的信息赋值返回
return OK; //返回操作成功信息
}
//数据结构 算法2.9---->指定位置插入一个元素
int insertLinkList(LinkList &list, int i, Student student)
{
int j = 1;
if(i < 1) return ERROR; //若插入位置小于1,返回ERROR失败信息,插入失败
LNode *p = list, *node; //定义p指针指向头节点,定义node结点作为插入结点
//通过循环找到待插入结点的前一个结点,即i-1个结点
for(j = 1; j < i && (p!=NULL); j++)
{
p = p->next;
}
if(p == NULL && j >= i) return ERROR; //若前一个结点为NULL或者插入位置超出范围表示插入失败,返回失败信息
node = (LNode *)malloc(sizeof(LNode)); //开辟一个新的结点内存信息
node->student = student; //保存插入元素的值
node->next = p->next; //将目前第i个位置的结点作为node结点的后继结点
p->next = node; //再将第i-1个结点的后继结点作为node结点,即插入第i个位置成功
return OK; //返回插入成功信息
}
//数据结构 算法2.10---->指定位置删除一个元素,并返回该元素的值
int deleteLinkList(LinkList &list, int i, Student &student)
{
int j = 1; //定义统计次数标识位j并初始化为1
if(i < 1) return ERROR; //如果删除位置小于1,则返回删除失败
LNode *p = list, *q; //定义p指针最先指向链表头指针
//循环遍历找到链表的第i-1个位置,准备进行插入
for(j = 1; j < i && (p->next!=NULL); j++)
{
p = p->next;
}
//如果第i个位置为null或者删除位置已超出,返回删除失败信息
if(p->next == NULL || j >= i) return ERROR;
student = p->next->student; //将待删除位置的结点信息赋值为student,待传回操作
q = p->next; //保存删除结点的位置信息
p->next = p->next->next; //直接将待删除结点的前一个结点的后继结点 连接到 待删除结点的后继节点(相当于删除)
free(q); //释放删除结点的地址信息数据
return OK; //返回删除成功
}
//数据结构 算法2.12---->合并两个有序单链表
int mergeLinkList(LinkList &list_a, LinkList &list_b, LinkList &list_c)
{
LNode *pa = list_a->next, *pb = list_b->next, *pc; //定义pa、pb、pc三个指针, pa、pb分别指向链表list_a, list_b
initLinkList(list_c); //初始化链表c
pc = list_c; //pc指向链表list_c
//若链表pa,pb都未合并完毕,重复循环插入
while(pa != NULL && pb != NULL)
{
if(pa->student.id <= pb->student.id) //按照升序进行有序合并,若pa指向结点学生的序号小于pb
{
pc->next = pa; //则把pa指向结点添加到pc指向链表之后
pc = pa; //pc移动到后一个结点(即pa),准备添加下一个结点
pa = pa->next; //pa添加过之后,指针也向后进行移动
}
else //反之亦然
{
pc->next = pb;
pc = pa;
pb = pb->next;
}
}
if(pa != NULL) pc->next = pa; //若最终pa指向链表未添加完毕,直接将pa剩余链表接到pc链表后
if(pb != NULL) pc->next = pb; //若最终pb指向链表未添加完毕,直接将pb剩余链表接到pc链表后
return OK; //返回成功信息
}
//打印所有结点
void printLinkList(LinkList &list)
{
int i = 0; //设置i作为记录结点个数
LNode *p = list->next;
while(p!=NULL) //循环输出所有结点
{
printf("id = %d.\tname = %s.\tage = %d.\n", p->student.id, p->student.name, p->student.age); //输出结点信息
p = p->next; //p指针后挪至下一结点信息
i++;
}
printf("输出链表列表完毕,共%d条数据.\n", i);
}
小结
总之,链表在插入和删除元素时较顺序存储较为方便,采用动态形式进行结点的扩充,对空间利用率也更高,但需要从头结点依次遍历查找,略有不便,当我们需要保存链表的长度信息时,也可以将其它信息保存到头结点中去,链表的形式还有很多,如循环链表、双向链表等,后续再慢慢更新上来!
加油,奥里给~