一篇教你搞懂--数据结构之线性表
前言
转眼已然寒假已经过了大半,寒假实现数据结构书上的所有算法的目标今天才开始实现。现在想所有几乎是不可能的了,故笔者想在开学之前(3.8)把数据结构一书上的主要算法实现一遍,过一遍目录大纲(由于这是关于数据结构文章的第一篇,这里指明教材版本—《数据结构(C语言版)》严蔚敏、吴伟民编著)。
这里发表一些感慨,也许是自己立下的flag太多,获是自己的自制力不够,亦或许是两者都有,导致现在的寒假目标的完成是遥遥无期了,一些任务只能移到开学之后,觉得自己lfw了。但是想来其实也挺努力了,不管怎么说,先行动起来,把握当下,尽自己的全力吧。
学习前提:在开启数据结构的学习之前,首先读者需要对C语言有一定的了解,之后的blog都是认为读者已经了解过C语言的基础上写的。如果读者还想对C语言有更深入的学习,读者也可以参看我之前写的关于C语言的blog专栏(里面为C语言的重点难点进行了详细的解读),这里提供链接:C语言
首先给大家送一波福利,笔者已经为读者找好资源,这里永久有效的百度网盘分享数据结构(C语言版)严蔚敏、吴伟民,提取码是:8aky。
有人说你这不是课本吗,为什么还有发文章的必要。这里笔者申明:笔者将核心和重点提炼出来,并奉上详细的解释和代码,这是你单独看课本所不能获得的。另一个也是笔者作为学习的总结,与读者一起分享讨论,如果接下来的阅读有什么问题均可以评论区留言或私信联系笔者解决。
还有一点需要说明的是,笔者将书籍中的重点进行讲解,提供实现的源码、注释和讲解。具体课本的细节,如果有想深究的读者还需自己潜心钻研;俗话说的好,师傅领进门,修行在个人。悉心提示:因为是一篇文章概括了所有笔者需要讲述的东西,故篇幅有一些长,读者可以根据目录进行对应的跳转阅读。话不多说,让我们一起进入正题吧。
①线性表类型和定义
正如课本P19所述,抽象数据类型线性表的定义如下。之后笔者会对其链式存储的实现进行讲解和并进行分享,因为顺序的已经是C语言中的基础了,在数据结构中就不再赘述了。但是下面定义和说明需要进行一定的了解:
②线性表的顺序表示和实现
线性表的顺序表示和实现最经典的就是用数组。关于①中的线性表定义的实现,读者可以根据③中对线性表的链式存储的解析来类比。这里简单说明一下:
线性表中最重要的无非就是增删改查,且是在数组的基础(又或者是结构体)上进行的。
增:判断是否可以数组书否已满,即是否可以再往里面添加元素,如果可以,在数组中找到指定位置进行加入。加入的时候分为三种情况:1.在数组的开头,此时需将数组的每一个元素往后移动一个单位,并加入新的元素。2.添加在数组的中间,此时将插入位置后的元素每个均向后移动一个单位,并加入要添加的元素。3.添加在末尾,直接添加即可。
删:删除的操作其实就是增加的逆操作,读者可根据增加元素的步骤,逆推出来。
改和查:根据所给数组的索引或元素的特征,寻找到相应位置,并进行修改或查询操作。
③线性表的链式表示和实现
根据①中的表格,我们给出其中关于链表实现的每一个操作的解释,实现方法和代码。
InitList()// 建立初始化链表,并返回表头指针。
print// 打印链表
以上的实现方法可在我曾经写过这篇文章中得到解决动态链表、建立与输出
ListEmpty// 判断L是否为空表
在已知的头指针之后,判断当前结构体指向的后一个指针是否为null,如果是,则为空表,反之亦然。对于返回值,我们可以使用0和1来分别表示空和不空。
ListLength // 返回线性表的长度
在这个操作中(函数)初始化一个计数count的变量且初始化为1(因为第一个头指针存在时,链表的长度已经为1了),指针每往后面指一个结构体变量就将其+1,知道null为止,最后返回count即可。
GetElem// 返回链表中指定序号的元素值
如法炮制ListLength的做法,指针每往后移一个元素就将count+1,并将其与给的序号值进行比较,如果相同则输出;如果搜寻完都没有找到,做出没有找到的提示。
PriorElem// 在链表中,返回元素value的前驱
对于指针p,如果p->next指向的是我们给定的value,那么我们就返回p
NextElem// 返回元素value的后继
对于指针p,如果p->next指向的是我们给定的value,那么我们就返回p->next
ListInsert// 元素插入到第i个位置,并且链表长度+1
找到需要插入的位置的指针p和p->next(找到p即找到p->next),为需要插入的元素创建空间,并分配值,这里设此开辟的空间的指针为q,下面进行插入(链接)工作;将p指向q,q指向p->next。
ListDelete// 删除第n个元素,并返回其值,链表长度-1
要完成删除工作,需要找到删除位置的指针和它前一个位置的指针,这里分别定义为q和p,之后只需将p->next指向q->next即可。
源码笔者放在最后(以免文章过于冗长),下面是运行截图:
④两个有序链表的链接 & 一元多项式的表示和操作
两个有序链表的链接
下面先给出课本的解释。
其实,笔者觉得,算法都是思想,有自己思想才是最重要的。笔者想的是(也为读者提供一些其他思路):链接两个有序链表,首先是创建一个函数,输入的参数是两个有序链表的头指针a,b和将要输出的链表头指针c(地址传递)。主要的工作是分别比较a和b中元素的值,将哪个最小d(a和b其中的一个)就将其赋值给c,c.value=d.value。之后做如此操作d=d->next,c=c->next。重复以上操作直到有一个链表已被链接完成(即null出现),将剩下的再连入c中即可。
下面是该程序的运行截图
一元多项式的表示和操作
一元多项式可以用数组来实现,但是如果像1000000001来说,如果用数组来记录每一位上的数时,则有很多0的地方,即数组没有得到充分利用,这时候链表就可以很好地解决这个问题。构建相应的结构体,记录系数和次方,并且有指针变量连接链表。这就实现的主要思想,之后的所有操作都是在此基础上完成的。
一元多项式的概念和实现其实和链表的差不多,只是有一些出入和修改的地方。具体看最后的代码。
CreatePolyn(int n); // 建立一元多项式函数
这个函数在创建链表的基础上,加入了项数n。所以我们链表的长度就是已知的了,而不是像动态链表那样以特殊的输入符来判断是否链表结束。节点的数据结构应该用以下这几个部分:幂次,系数和指向下一个节点的指针。
print(struct mylist *p); // 打印一元多项式
根据给定的函数头指针来一次遍历整个链表,根据不同节点的幂次和系数,输出即可。
PolynLength(struct mylist *p); // 返回一元多项式项数
用一个while来一直遍历到链表的末尾,用count来计数,且初值为1,指针每往后移动一个位置就将count的值+1,直到寻找null并结束。
AddPolyn(struct mylist *pa,struct mylist *pb); // 多项式相加
这里我们把b合并到a上,所以我们需要合并的次数是b链表的长度,此时函数需要调用到之前定义的函数PolynLength来得出b链表的长度。之后再length(b)次的循环遍历,每次从a的头节点开始依次合并;幂次一样的系数相加,遍历完幂次都不一样的则创建新的节点并连接到链表的最后。
MultiplyPolyn(struct mylist *pa,struct mylist *pb); // 多项式相乘
该函数与AddPloyn大同小异,合并的次数应该是a和b长度的乘积,只是变得复杂了一点。
下面是运行截图:
这里再提一提双向链表和循环链表的概念,他们的实现就留给读者作为思考和时间的作业叭,他们都是在基本链表上的修改和延申。
双向链表:其与单向链表不同的是,每个节点有分别指向他们的前驱和后继的指针,可以双向进行操作。
循环链表:基本的单项链表的末尾是null,而循环链表的最后一个指针,它指向的是头节点,这样就完成了头和尾的链接,顾名思义——循环。
当然在实现他们的时候就要注意啦,这里给上其结构图,给读者更直观上的理解。
终于到代码啦
如果笔者觉得下面的代码麻烦,也可以在评论区留下邮箱或私信笔者,免费且及时为大家提供源码的发送。
话不多说,看代码!
链表的顺序表示和实现
#include<stdio.h>
#include<malloc.h>
#define LEN sizeof(struct mylist) // 链表长度,在开辟空间时使用
int number; // 学生序号
struct mylist{ // 定义结构体mylist
int num; // 序号
int grade; // 成绩
struct mylist *next; // 指针,用于连接
};
int main(){
// 函数声明
struct mylist *InitList(); // 建立初始化链表,并返回表头指针
void print(struct mylist *head); // 打印链表
bool ListEmpty(struct mylist *head); // 判断L是否为空表
int ListLength(struct mylist *p); // 返回线性表的长度
int GetElem(struct mylist *head,int i); // 返回链表中指定序号的元素值
struct mylist *PriorElem(struct mylist *head,int value); // 在链表中,返回元素value的前驱
struct mylist *NextElem(struct mylist *head,int value); // 返回元素value的后继
struct mylist *ListInsert(struct mylist *head,int n,int value); // 元素插入到第i个位置,并且链表长度+1
struct mylist *ListDelete(struct mylist *head,int n); // 删除第n个元素,并返回其值,链表长度-1
// test
struct mylist *p;
p = InitList(); // 初始化链表
print(p); // 打印链表
bool flag = ListEmpty(p); // 检查链表是否为空
printf("the list isempty:%d\n",flag);
int g = GetElem(p,2); // 获得链表的第2个元素
printf("the NO.2 element in list is:%d\n",g);
int l = ListLength(p); // 获得线性表的长度
printf("length is:%d\n",l);
struct mylist *before = PriorElem(p,3); // 获得第三个元素的前驱
printf("before is %d\n",before->grade);
struct mylist *after = NextElem(p,3); // 获得第三个元素的后继
printf("before is %d\n",after->grade);
p = ListInsert(p,3,3); // 插入元素
printf("insert:\n");
print(p);
p = ListDelete(p,3); // 删除元素
print(p);
return 0;
}
struct mylist *InitList(){
// 建立初始化链表,并返回表头指针
struct mylist *head;
struct mylist *p1,*p2;
head = p1 = p2 = (struct mylist*)malloc(LEN);
p2->num = ++number;
printf("input %d grade:",number);
scanf("%d",&p2->grade);
if(p2->grade == 0){
head = NULL;
}
while(p2->grade != 0){
p1 = p2;
p2 = (struct mylist*)malloc(LEN);
p1->next = p2;
p2->num = ++number;
printf("input %d grade:",number);
scanf("%d",&p2->grade);
}
p1->next = NULL;
number = 0;
return head;
}
void print(struct mylist *head){
// 打印链表
struct mylist *p = head;
while(p != NULL){
printf("%d grade is %ld\n",p->num,p->grade);
p = p->next;
}
}
bool ListEmpty(struct mylist *head){
// 判断L是否为空表
if(head == NULL){
return false;
}
return true;
}
int ListLength(struct mylist *p){
// 返回线性表的长度
int n = -1;
if(p == NULL){
return n;
}
while(p->next != NULL){
p = p->next;
}
n = p->num;
return n;
}
int GetElem(struct mylist *head,int i){
// 返回链表中指定序号的元素值
int value;
struct mylist *p = head;
while(p->num != i){
p = p->next;
}
value = p->grade;
return value;
}
struct mylist *PriorElem(struct mylist *head,int value){
// 在链表中,返回元素value的前驱
struct mylist *p = head,*temp;
if(p->num == 0){
return NULL;
}
temp = p->next;
while(temp->num != value){
p = temp;
temp = p->next;
}
return p;
}
struct mylist *NextElem(struct mylist *head,int value){
// 返回元素value的后继
struct mylist *p = head;
while(p->grade != value){
p = p->next;
}
return p->next;
}
struct mylist *ListInsert(struct mylist *head,int n,int value){
// 元素插入到第i个位置,并且链表长度+1
int l = ListLength(head);
struct mylist *p = head,*temp;
temp = (struct mylist*)malloc(LEN);
temp->num = n;
temp->grade = value;
if(n == 1){
temp->next = p;
do{
p->num = p->num + 1;
p = p->next;
}while(p->next != NULL);
return temp;
}else{
while(p->num != n-1){
p = p->next;
}
temp->next = p->next;
p->next = temp;
temp = temp->next;
do{
temp->num = temp->num + 1;
temp = temp->next;
}while(temp->next != NULL);
temp->num = temp->num + 1;
return head;
}
}
struct mylist *ListDelete(struct mylist *head,int n){
// 删除第n个元素,并返回其值,链表长度-1
struct mylist *p = head,*temp;
int value;
if(n == 1){
value = p->grade;
p = p->next;
}else{
while(p->num != n-1){
p = p->next;
}
temp = p->next;
value = (p->next)->grade;
p->next = temp->next;
p = head;
temp = temp->next;
while(temp->next != NULL){
temp->num = temp->num - 1;
temp = temp->next;
}
temp->num = temp->num - 1;
}
printf("the deleted grade is:%d\n",value);
return p;
}
两个有序俩表的链接
#include<stdio.h>
#include<malloc.h>
#define LEN sizeof(struct list) // 链表长度
struct list{
int number; // 数据
struct list *next; // 指针
};
int main()
{
// 将两个有序链表和并为一个有序链表
// 初始申明
struct list *InitList(); // 建立初始化链表,并返回表头指针
void print(struct list *head); // 打印链表
void sort(struct list *pa,struct list *pb,struct list *pc);
// 建立两个有序链表并打印,a为1,3;b为2,4,c为0
struct list *pa,*pb,*pc;
pa = InitList();
print(pa);
pb = InitList();
print(pb);
pc = InitList();
print(pc);
printf("the sorted numbers are:\n");
// 归并打印(从小到大)
sort(pa,pb,pc); // 合并两个有序线性表的排序函数
print(pc);
return 0;
}
struct list *InitList(){
// 建立初始化链表,并返回表头指针
struct list *head;
struct list *p1,*p2;
head = p1 = p2 = (struct list*)malloc(LEN);
printf("input number:");
scanf("%d",&p2->number);
if(p2->number == 0){
head = NULL;
}
while(p2->number != 0){
p1 = p2;
p2 = (struct list*)malloc(LEN);
p1->next = p2;
printf("input number(input 0 to end):");
scanf("%d",&p2->number);
}
p1->next = NULL;
return head;
}
void print(struct list *head){
// 打印链表
struct list *p = head;
while(p != NULL){
printf("number is %d\n",p->number);
p = p->next;
}
}
void sort(struct list *p1,struct list *p2,struct list *p3)
{
struct list *pa = p1;
struct list *pb = p2;
struct list *pc = p3;
while(pc->next != NULL){
pc = pc->next;
}
// 合并两个有序线性表的排序函数(从小到大)
while(pa && pb){
if(pa->number > pb->number){
pc ->next = pb;
pc = pb;
pb = pb->next;
}else{
pc ->next = pa;
pc = pa;
pa = pa->next;
}
}
pc->next = pa ? pa:pb;
}
一元多项式
#include<stdio.h>
#include<malloc.h>
#define LEN sizeof(struct mylist)
struct mylist{
int factor;
int power;
struct mylist *next;
};
int main()
{
// 基础申明
struct mylist *CreatePolyn(int n); // 建立一元多项式函数
void print(struct mylist *p); // 打印一元多项式
int PolynLength(struct mylist *p); // 返回一元多项式项数
void AddPolyn(struct mylist *pa,struct mylist *pb); // 多项式相加
//void MultiplyPolyn(struct mylist *pa,struct mylist *pb); // 多项式相乘
// 测试实例
struct mylist *p1,*p2;
p1 = CreatePolyn(2);
p2 = CreatePolyn(3);
print(p1);print(p2);
printf("length1 is %d ,length2 is %d",PolynLength(p1),PolynLength(p2));
AddPolyn(p1,p2);
//MultiplyPolyn(p1,p2);
print(p1);
return 0;
}
struct mylist *CreatePolyn(int n)
{
// 建立一元多项式函数
int i=1,num1,num2;
struct mylist *p1,*p2,*head;
head = p1 = p2 = (struct mylist*)malloc(LEN);
for(;i<=n;i++){
printf("%dth, please input factor and power:",i);
scanf("%d,%d",&num1,&num2);
p2->factor = num1;
p2->power = num2;
if(i == n){
p2->next = NULL;
break;
}
p2 = (struct mylist*)malloc(LEN);
p1->next = p2;
p1 = p2;
}
return head;
}
void print(struct mylist *p)
{
// 打印一元多项式
printf("the polynomial is:");
while(p->next != NULL){
printf("%d*x^%d + ",p->factor,p->power);
p = p->next;
}
printf("%d*x^%d\n",p->factor,p->power);
}
int PolynLength(struct mylist *p)
{
// 返回一元多项式项数
while(p->next != NULL){
p = p->next;
}
return p->power;
}
void AddPolyn(struct mylist *p1,struct mylist *p2)
{
// 多项式相加
int length1 = PolynLength(p1);
int length2 = PolynLength(p2);
int flag = 0; // 用于判断每次是否相加成功
struct mylist *p,*pa,*pb;
pa = p1;
pb = p2;
for(int i = 1;i <= length2;i++){
// 把b合并到a上
while(pa->next != NULL){
if(pa->power == pb->power){
//如果二者相等,直接系数相加
pa->factor = pa->factor + pb->factor;
flag = 1;
break;
}
pa = pa->next;
}
if(pa->power == pb->power && flag == 0){
//如果二者相等,直接系数相加
pa->factor = pa->factor + pb->factor;
flag = 1;
}
if(flag == 0){
p = (struct mylist*)malloc(LEN);
p->factor = pb->factor;
p->power = pb->power;
pa->next = p;
p->next = NULL;
}
pa = p1;
if(pb->next == NULL){
break;
}
pb = pb->next;
flag = 0;
}
}