原文章:
数据结构学习记录:第二章:线性表 - 知乎 (zhihu.com)
此文章为作者本人搬运至该网站。
说在前头:
各位在学习过程中,如果有任何不懂的地方,可以随时评论或者私信我!!!我每天都在高强度网上冲浪(这一点真的属实),从各位进行评论,到我发现评论,我认为应该最多不超过半个小时,所以啊,各位,尽情用你们的评论和私信淹没我吧!(=・ω・=)
线性表大致分为普通线性表和链式线性表(也就是链表),在这里我就直接区分为线性表和链表了,下面先从线性表开始讲。
1.线性表的定义、创建、输入还有输出
(1) 定义与创建:
线性表,简单来说就是一些数据元素的有限序列。而最简单的线性表,其实就可以当作 数组 来看,但是毕竟它只能一个一个存储同类型的数据。那么就需要另一种东西来存储多种数据而且能够可以实现顺序表示,你想到c语言里面的什么了嘛?
。。。
。。。
。。。
就是结构体哒!!!
而结构体将会贯穿你学习数据结构的整个过程,一定要认真记住!
有了这个前提后,我还要再提一句,就是一翻开线性表这一章首页就能看到的这个:
有没有一种熟悉的感觉?
没错,这些内容还是不能用!和上面的原因一样,需要你自己手敲来实现这些函数。我个人这里的内容如果不先讲如何创建线性表,让我们知道线性表其实是用结构体实现的,反而讲这些抽象的定义,反而我是看不懂= =
那么具体是如何创建的呢,课本翻到22页!
//-----线性表的动态分配顺序存储结构------
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
typedef struct {
ElemType * elem; //存储空间基址(就是该结构体储存的数据)
int length; //当前长度
int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位
}SqList;
这个typedef在上一章有讲解,不过是对于普通的数据类型来用的,而这里变成了结构体,那么就进一步讲一点吧。
其实这个
typedef struct {
int a;
}SqList;
就和
struct SqList{
int a;
};
的意义其实是相同的,后者是定义了个名字为SqList的结构体(虽然之前我们往往会忽略给结构体起名字,因为主要是用到结构体变量),而前者是简化了定义结构体的流程,其实尾部的那个SqList并不是该结构体的变量,而是用typedef定义的结构体的名字。
这样的话前者定义结构体变量就只需要用
typedef struct {
int a;
}SqList;
SqList x;
而后者要用:
struct SqList{
int a;
};
struct SqList x;
多了个struct(除非结构体变量是在定义结构体的时候就定义好的了,就不需要用到typedef了)。而它们两种对结构体内部变量的使用都是一样的,就是:
scanf("%d", &x.a);
printf("%d", x.a);
可以这样直接使用,那么这个结构体就创建成功了。可是这样还不能进行操作顺序表,因为结构体没有开辟空间!
那么就需要写一个开辟空间---亦或者是创建空的线性表的函数。
(2) 开辟空间:
Status InitList_Sq(SqList &L) {
//构造一个空的线性表L
L.elem = (ElemType * )malloc(LIST_INIT_SIZE * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW); //存储分配失败
L.length = 0; //空表长度为0
L.listsize = LIST_INIT_SIZE; //初始存储容量
return OK;
}// InitList_Sq
其实这些内容和第一章中创建三元组的函数差不多,L.elem指向一个长度为 LIST_INIT_SIZE 的数组,开辟了这么多空间,但由于没有数据,所以 L.length 先至为0,而初始长度容量也很容易理解,其实就是储存一个数组长度。这样空的线性表我们也创造出来了。
但是还不够!!!因为我们没有写入数据!课本中我一直都找不到如何写入数据,可能老师觉得这是个基本功吧。。。。。
所以在这里,经过自己尝试,我也比葫芦画瓢做出来一个输入线性表数据的函数和输出线性表数据的函数。
(3) 输入与输出:
Status ScanfList_Sq(SqList &L) { //在顺序表中输入一串整数,以输入负数为结尾
int i = 0; //其实这里的左边的Status就可以换成int,只不过还是按规矩来吧
while(1){ //持续输入数据
if (L.length >= L.listsize){
//判断输入数据的个数是否超出线性表存储空间的初始分配量
//如果超出了,就用下面的realloc函数增加分配空间
L.elem = (ElemType * )realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW);
L.listsize += LISTINCREMENT;
//这里通过自加来扩大表示出的存储量
}
scanf("%d", &L.elem[i]); //正常输入数据
if (L.elem[i] < 0){ //直到输入的数字为负数
break; //则退出循环
} //此时L.listsize保存的数就是当前输入了多少个整数
else{
i++; //而如果当前输入的数不是负数,则数据个数自加
L.length++; //之后继续循环
}
}
return OK; //这里的OK忘了吗? #define OK 1
}// ScanfList_Sq
在这里我们又遇到了一个新家伙,realloc函数,我先贴上详细版的教程:
realloc() 用法详解_realloc用法-优快云博客
而我自己来看的话,就是右半部分比malloc多了个原指针,以及扩大后的内存分配量。在扩大分配后,原先已经输入的数据不会消失或清空,除非是把内存减少。而这里的 if(L.length >= L.listsize) 的判断是为了保证输入的数据不会因为过多而突破原先分配的内存后导致保存不下,用realloc增加内存之后显然可以继续存储了。
之后是输出函数:
Status PrintfList_Sq(SqList &L) { // 输出刚才的一串数字(不输出最后输入的负数)
for (int i = 0; i < L.length; i++){ //而这个L.length就是上面输入函数所保存的
printf("%d ", L.elem[i]);
}
return OK;
}// PrintfList_Sq
很简单,一笔带过。
接下来就是我们震撼人心的时刻,把这些全部放到一起,开始运行!!!
在这里,你将实现你的第一个数据结构的可以成功运行的代码!
#include <stdio.h>
#include <stdlib.h>
//-----线性表的动态分配顺序存储结构------
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
#define OVERFLOW -2
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct {
ElemType * elem; //存储空间基址
int length; //当前长度
int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位
}SqList;
Status InitList_Sq(SqList &L) {
//构造一个空的线性表L
L.elem = (ElemType * )malloc(LIST_INIT_SIZE * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW); //存储分配失败
L.length = 0; //空表长度为0
L.listsize = LIST_INIT_SIZE; //初始存储容量
return OK;
}// InitList_Sq
Status ScanfList_Sq(SqList &L) { //在顺序表中输入一串整数,以输入负数为结尾
int i = 0; //其实这里的左边的Status就可以换成int,只不过还是按规矩来吧
while(1){ //持续输入数据
if (L.length >= L.listsize){
//判断输入数据的个数是否超出线性表存储空间的初始分配量
//如果超出了,就用下面的realloc函数增加分配空间
L.elem = (ElemType * )realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW);
L.listsize += LISTINCREMENT;
//这里通过自加来扩大表示出的存储量
}
scanf("%d", &L.elem[i]); //正常输入数据
if (L.elem[i] < 0){ //直到输入的数字为负数
break; //则退出循环
} //此时L.listsize保存的数就是当前输入了多少个整数
else{
i++; //而如果当前输入的数不是负数,则数据个数自加
L.length++; //之后继续循环
}
}
return OK; //这里的OK忘了吗? #define OK 1
}// ScanfList_Sq
Status PrintfList_Sq(SqList &L) {
for (int i = 0; i < L.length; i++){
printf("%d ", L.elem[i]);
}
return OK;
}// PrintfList_Sq
int main(){
SqList L;
InitList_Sq(L);
ScanfList_Sq(L);
PrintfList_Sq(L);
return 0;
}
可如果你把这些代码复制粘贴到dev c++中,却可能发现还是无法运行,甚至报错!
这是因为定义函数里面对于结构体的引用 PrintfList_Sq(SqList &L) ,c语言不支持!!
而把文件后缀改成cpp就能通过了(dev c++创建的文件一开始就是cpp,这个问题应该不会遇到),而说来也挺好笑,一个基于c语言的课本里面的内容却要用c++运行。。。真无语了。。。
而这个小问题解决后,我保证这段代码完全可以复制粘贴来用!没有任何问题!
搞清楚数据的输入和输出,我们才可以着手于数据的插入还有删除等等。
2.线性表中元素的插入、删除
现在我们再来看课本上面对线性表的操作,是不是感觉容易理解了一些?我们继续来看。
(1) 元素插入:
但是且慢!你有没有发现这个函数里面有个奇怪的地方?
。。。
newbase又是个什么玩意儿???
这里我发誓,我从这本书的这个函数所在的页数往回翻,均没有找到关于newbase的定义,而下面的q和p也没有!
好好好这么让我们学习是吧。。。
那么这个newbase,其实就是新的指针元素,用这个指针指向一个用realloc扩容后的数组。而下面的q和p也都是指针,指向的是数组中某一元素的插入位置。
而既然这三个是新东西,而又只使用在这个函数中,所以我们再在这个函数中加上这三个指针的定义。
/* 这样的注释表明是我自己写的 */
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
//在顺序线性表L中第 i个位置之前插入新的元素e,
//i的合法值为 1 <= i <= ListLength_Sq(L) + 1 /*我真服了左边这个函数同样在课本上找不到定义*/
ElemType * newbase; /*对这三个指针元素的定义 */
ElemType * q;
ElemType * p;
if (i < 1 || i > L.length + 1) return ERROR; //i值不合法
if (L.length >= L.listsize) { //当前存储空间已满,增加分配
newbase = (ElemType * )realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW); //存储分配失败
L.elem = newbase; //新基址
L.listsize += LISTINCREMENT; //增加存储容量
}
q = &(L.elem[i - 1]); //q为插入位置
for (p = &(L.elem[L.length - 1]); p >= q; --p){
*(p + 1) = *p; //插入位置及之后的元素右移
/*这里的for循环我实在看不下去了自己加了个括号,真就那么想省纸搞得大家都难受?*/
}
*q = e; //插入e
++L.length; //表长增1
return OK;
}// listInsert_Sq
而要理解起来其实也挺简单,第一个if语句判断插入的数字的位置是否在线性表里面,第二个if语句判断是不是插在了队尾,如果是的话那就要增加所分配的空间,realloc在第一节里面也讲过。下面的for循环则是用到两个指针来把所插入的数据的 后面所有的数据 一个一个往后边移动,为该将要插入的数据腾出一个位置,之后把该数据插入到指定位置。表长自加1。
(2) 元素删除:
Status ListDelete_Sq(SqList &L, int i, ElemType &e) {
//在顺序线性表L中删除第i个元素,并用e返回其值
//i的合法值为 1 <= i <= ListLength_Sq(L) + 1
ElemType * q; /*对p和q的指针元素的定义 */
ElemType * p;
if (i < 1 || i > L.length + 1) return ERROR; //i值不合法
p = &(L.elem[i - 1]); //p为被删除元素的位置
e = *p; //被删除元素的值赋给e
q = L.elem + L.length - 1; //表尾元素的位置
for (++p; p <= q; ++p) {
*(p - 1) = *p; //被删除元素之后的元素左移
}
--L.length; //表长减1
return OK;
}// listDelete_Sq
在避过了上面定义的坑之后,删除也变得挺简单的了,对p和e还要q的赋值应该都能看懂,而下面的for循环中的第一个++p则是把指向的元素位置向后移动一格,便于循环中的元素左移,而如果不写这个++p,里面循环内容也可以改成 *p = *(p + 1); 只不过循环判定的 p <= q 就要去掉等于号了。
(3) 现在把这些内容合并起来:
把这些插入与删除和之前的输入与删除合并起来会怎样?
那就是第二个可以运行的代码!
#include <stdio.h>
#include <stdlib.h>
//-----线性表的动态分配顺序存储结构------
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
#define OVERFLOW -2
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct {
ElemType * elem; //存储空间基址
int length; //当前长度
int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位
} SqList;
//函数的引用
Status InitList_Sq(SqList &L);
Status ScanfList_Sq(SqList &L);
Status PrintfList_Sq(SqList &L);
Status ListInsert_Sq(SqList &L, int i, ElemType e);
Status ListDelete_Sq(SqList &L, int i, ElemType &e);
int main() {
SqList L;
InitList_Sq(L);
printf("请输入一些整数,以输入负数为结束:\n");
ScanfList_Sq(L);
printf("输出这些整数:\n");
PrintfList_Sq(L);
int i, e;
printf("\n请输入想要插入的整数的位置以及要插入的整数:\n");
scanf("%d %d", &i, &e);
ListInsert_Sq(L, i, e);
printf("输出这些整数:\n");
PrintfList_Sq(L);
printf("\n请输入想要删除的整数的位置:\n");
scanf("%d", &i);
ListDelete_Sq(L, i, e);
printf("所删除的数据:%d\n", e);
printf("输出这些整数:\n");
PrintfList_Sq(L);
return 0;
}
Status InitList_Sq(SqList &L) {
//构造一个空的线性表L
L.elem = (ElemType * )malloc(LIST_INIT_SIZE * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW); //存储分配失败
L.length = 0; //空表长度为0
L.listsize = LIST_INIT_SIZE; //初始存储容量
return OK;
}// InitList_Sq
Status ScanfList_Sq(SqList &L) { //在顺序表中输入一串整数,以输入负数为结尾
int i = 0; //其实这里的左边的Status就可以换成int,只不过还是按规矩来吧
while(1) { //持续输入数据
if (L.length >= L.listsize) {
//判断输入数据的个数是否超出线性表存储空间的初始分配量
//如果超出了,就用下面的realloc函数增加分配空间
L.elem = (ElemType * )realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW);
L.listsize += LISTINCREMENT;
//这里通过自加来扩大表示出的存储量
}
scanf("%d", &L.elem[i]); //正常输入数据
if (L.elem[i] < 0) { //直到输入的数字为负数
break; //则退出循环
} //此时L.listsize保存的数就是当前输入了多少个整数
else {
i++; //而如果当前输入的数不是负数,则数据个数自加
L.length++; //之后继续循环
}
}
return OK; //这里的OK忘了吗? #define OK 1
}// ScanfList_Sq
Status PrintfList_Sq(SqList &L) {
for (int i = 0; i < L.length; i++) {
printf("%d ", L.elem[i]);
}
return OK;
}// PrintfList_Sq
Status ListInsert_Sq(SqList &L, int i, ElemType e) {
//在顺序线性表L中第 i个位置之前插入新的元素e,
//i的合法值为 1 <= i <= ListLength_Sq(L) + 1 /*我真服了左边这个函数同样在课本上找不到定义)*/
ElemType * newbase; /*对这三个指针元素的定义 */
ElemType * q;
ElemType * p;
if (i < 1 || i > L.length + 1) return ERROR; //i值不合法
if (L.length >= L.listsize) { //当前存储空间已满,增加分配
newbase = (ElemType * )realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
if(! L.elem) exit(OVERFLOW); //存储分配失败
L.elem = newbase; //新基址
L.listsize += LISTINCREMENT; //增加存储容量
}
q = &(L.elem[i - 1]); //q为插入位置
for (p = &(L.elem[L.length - 1]); p >= q; --p) {
*(p + 1) = *p; //插入位置及之后的元素右移
/*这里的for循环我实在看不下去了自己加了个括号,真就那么想省纸搞得大家都难受?*/
}
*q = e; //插入e
++L.length; //表长增1
return OK;
}// listInsert_Sq
Status ListDelete_Sq(SqList &L, int i, ElemType &e) {
//在顺序线性表L中删除第i个元素,并用e返回其值
//i的合法值为 1 <= i <= ListLength_Sq(L) + 1
ElemType * q; /*对p和q的指针元素的定义 */
ElemType * p;
if (i < 1 || i > L.length + 1) return ERROR; //i值不合法
p = &(L.elem[i - 1]); //p为被删除元素的位置
e = *p; //被删除元素的值赋给e
q = L.elem + L.length - 1; //表尾元素的位置
for (++p; p <= q; ++p) {
*(p - 1) = *p; //被删除元素之后的元素左移
}
--L.length; //表长减1
return OK;
}// listDelete_Sq
在这里我加上了函数的引用,以便main函数能放在前面,而且又加上了一些汉字提示:
虽然我们忙活了这么久,可是也才到插入和删除,甚至还没到链表!
慢慢学吧。。。
3.另一种线性表数据输入方法---用插入的方法输入(以PTA一道题为例)
本以为我已经了解了线性表的大概,信誓旦旦准备去挑战老师布置的PTA的时候,我却发现,魔高一尺,道高一丈!
这道题是个函数题,简单来说就是给定了一些函数以及main函数,只需要让你写所要求的函数就可以。
先附上裁判测试程序样例:(其实就是写好的部分函数)
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 5
#define ERROR -1
typedef enum {false, true} bool;
typedef int ElementType;
typedef int Position;
typedef struct LNode *List;
struct LNode {
ElementType Data[MAXSIZE];
Position Last; /* 保存线性表中最后一个元素的位置 */
};
List MakeEmpty();
Position Find( List L, ElementType X );
bool Insert( List L, ElementType X, Position P );
bool Delete( List L, Position P );
int main()
{
List L;
ElementType X;
Position P;
int N;
L = MakeEmpty();
scanf("%d", &N);
while ( N-- ) {
scanf("%d", &X);
if ( Insert(L, X, 0)==false )
printf(" Insertion Error: %d is not in.\n", X);
}
scanf("%d", &N);
while ( N-- ) {
scanf("%d", &X);
P = Find(L, X);
if ( P == ERROR )
printf("Finding Error: %d is not in.\n", X);
else
printf("%d is at position %d.\n", X, P);
}
scanf("%d", &N);
while ( N-- ) {
scanf("%d", &P);
if ( Delete(L, P)==false )
printf(" Deletion Error.\n");
if ( Insert(L, 0, P)==false )
printf(" Insertion Error: 0 is not in.\n");
}
return 0;
}
/* 你的代码将被嵌在这里 */
注意最后一行的 /* 你的代码将被嵌在这里 */ ,这个说明了你只需要写其中几个函数就可以了,而所要写的函数就是题目中所要求的:
List MakeEmpty(); //创建一个空结构体
Position Find( List L, ElementType X ); //找到并return出线性表L中元素X的位置
boolInsert( List L, ElementType X, Position P ); //将元素X插入到线性表L中第P个位置
boolDelete( List L, Position P ); //删除线性表L中第P个位置的元素
同时这些函数也有其他的要求,Find中找不到元素就返回 ERROR,Insert中线性表已经满了就返回false等,需要你仔细思考如何做到全部内容。而这些和我们的 2. 中的内容讲的类似,但也有不同。
输入样例:
6
1 2 3 4 5 6
3
6 5 1
2
-1 6
输出样例:
FULL Insertion Error: 6 is not in.
Finding Error: 6 is not in.
5 is at position 0.
1 is at position 4.
POSITION -1 EMPTY Deletion Error.
FULL Insertion Error: 0 is not in.
POSITION 6 EMPTY Deletion Error.
FULL Insertion Error: 0 is not in.
限制:
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
如果你有耐心的话不妨尝试一下做这道题,
我要说的重点在这里:
(1):结构体指针
typedef int Position;
typedef struct LNode *List; /*用typedef定义了一个结构体指针的名字*/
struct LNode {
ElementType Data[MAXSIZE]; /*所储存的数据,即实际意义上的线性表*/
Position Last; /* 保存线性表中最后一个元素的位置 */
};
这种结构体的定义和我们之前用到的不一样:
typedef struct {
ElemType * elem; //存储空间基址(就是该结构体储存的数据)
int length; //当前长度
int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位
}SqList;
上面的结构体中不再使用指针,而是用到了已经定义好长度的数组,并且用Last储存最后一个元素的位置,而这个
typedef struct LNode *List;
则是定义了一个指向结构体的指针的定义名,之后用List L ,这个L就成为了指向这个结构体LNode的指针。
但是由于它只是个指针,结构体虽然定义了,但是并没有分配空间,所以在函数里面,我们要在定义指针的同时给结构体分配内存空间,并用这个指针指向该内存空间,所以这是另一种定义结构体变量并为其开辟空间的方法:
List MakeEmpty() {
List L;
L = (struct LNode* )malloc(sizeof(struct LNode));
//上面这个可以改成 L = (List* )malloc(sizeof(struct LNode));
//但是后面的那个LNode不能改
L->Last = -1;
return L;
}
这里是题目所要求写出的第一个函数,即创建出一个空结构体,并用指针指向它。(注释里面的内容记住就行了,我具体也不知道为什么_ (:з」∠)_
而我们要用到里面的元素,就是通过:
L->Data[i];
L->Last; /* 保存线性表中最后一个元素的位置 */
来实现。
而Find函数应该就不用多说了,一个for循环和if语句就能找到所要找的元素,for循环遍历一遍后还是找不到就返回ERROR。
下面我们就重点讲这个Insert函数,也就是用写入后插入的方法来代替直接写入。
(2):用插入的方法代替输入
先贴上main函数中的内容(写这样的函数题都是要先看main函数的,找到哪里使用到了需要你写的函数):
L = MakeEmpty();
scanf("%d", &N);
while ( N-- ) {
scanf("%d", &X);
if ( Insert(L, X, 0)==false )
printf(" Insertion Error: %d is not in.\n", X);
}
这里是第一个scanf输入数据的地方,而第一个scanf,结合下面的while循环,显然是让输入需要输入数据的个数,循环里面的scanf再进行数据的一个个输入。在if语句里面就会用到我们需要写的Insert函数,但是有一个需要费心思的地方就是,
它这个Insert函数,是一直都插在0的位置,也就是第一个元素的位置!
但是还好,我们知道插入元素的原理,就是把要插入元素的地方的后面的所有元素都往后移动一格。于是我们便可以开始尝试。
同时我们还要做到其他的要求:
bool Insert( List L, ElementType X, Position P ):将X插入在位置P并返回true。若空间已满,则打印“FULL”并返回false;如果参数P指向非法位置,则打印“ILLEGAL POSITION”并返回false;
由于我们的表的位置是从0开始的,那么一开始L->Last的值为-1,在插入一个元素后,L->Last就可以自加1。那么插入到何种地步才算插完了呢?别忘了之前用define定义的MAXSIZE为5,那么当L->Last到4的时候,就可以看作是插满了。而插入的位置判断也很容易看出,在这里放出Insert函数内容:
bool Insert( List L, ElementType X, Position P ){
if (L->Last + 1 == MAXSIZE){ //MAXSIZE为5,当L->Last的值变成4的时候,再判断就成FULL了
printf("FULL");
return false;
}
if(P < 0 || P > L->Last + 1){ //小于0很好理解,大于 L->Last + 1 的话
printf("ILLEGAL POSITION"); //如果P 等于 L->Last ,也就是说P要插在 L->Last 的位置,而 这个位置已经有数据了
return false; //那么下面的for循环就只需要走一次即可
} //而如果P等于 L->Last + 1 ,那么for循环就不需要走了,因为这个位置本来j就没数据
for (int i = L->Last; i >= P; i--){ //直接插入就可以了
L->Data[i + 1] = L->Data[i];
}
L->Data[P] = X; //在所要插入的位置插入
L->Last++; //表尾位置自加
return true;
}
这样就可以了,看不懂的可以顺着代码的逻辑一步步推导,身边有纸和笔最好,可以自己演算一遍。
而delete函数也很简单的,只需要把所要删除的数据的位置后面的所有数据全部往前移动一格,这样那个位置的数据就被覆盖了,正好达到删除的效果。
(3):最终实现
了解完之后,我们就可以写出来这几个函数了,你可以把这些函数和上面原先定义好的main函数等合并起来自测运行一下,这里为了节省篇幅就不放了~
List MakeEmpty() {
List L;
L = (struct LNode* )malloc(sizeof(struct LNode));
//上面这个可以改成 L = (List* )malloc(sizeof(struct LNode));
//但是后面的那个LNode不能改
L->Last = -1;
return L;
}
Position Find( List L, ElementType X ){
for (int i = 0; i <= L->Last; i++){
if (L->Data[i] == X){
return i;
}
}
return ERROR;
}
bool Insert( List L, ElementType X, Position P ){
if (L->Last + 1 == MAXSIZE){ //MAXSIZE为5,当L->Last的值变成4的时候,再判断就成FULL了
printf("FULL");
return false;
}
if(P < 0 || P > L->Last + 1){ //小于0很好理解,大于 L->Last + 1 的话
printf("ILLEGAL POSITION"); //如果P 等于 L->Last ,也就是说P要插在 L->Last 的位置,而 这个位置已经有数据了
return false; //那么下面的for循环就只需要走一次即可
} //而如果P等于 L->Last + 1 ,那么for循环就不需要走了,因为这个位置本来j就没数据
for (int i = L->Last; i >= P; i--){ //直接插入就可以了
L->Data[i + 1] = L->Data[i];
}
L->Data[P] = X; //在所要插入的位置插入
L->Last++; //表尾位置自加
return true;
}
bool Delete( List L, Position P ){
if(P < 0 || P > L->Last){
printf("POSITION %d EMPTY", P);
return false;
}
for (int i = P; i < L->Last + 1; i++){
L->Data[i] = L->Data[i + 1];
}
L->Last--;
return true;
}
对了,还要提示一下,题目写好的代码中的:
typedef enum {false, true} bool;
放在devc++中是无法运行的,无论是.c还是.cpp,应该是因为false和true是已经定义好的,不能重定义,复制粘贴的时候直接把这一行删除也可以正常运行。
4.课本上的归并
既然插入的原理都讲的差不多了,也是因为时间问题,我现在的进度跟老师讲的进度差了十万八千里,这里我就直接附上代码了,有一些迷糊人的地方我会指出,要赶紧开始链表部分了!
以下是我“解压”之后的代码:
void MergeList_ Sq(SqList La, SgList Lb, SgList &Lc) {
//已知顺序线性表 La 和 Lb 的元素按值非递减排列
//归并 La和 Lb得到新的顺序线性表 Lc,Lc的元素也按值非递减排列
ElemType *pa = La.elem;
ElemType *pb = Lb.elem;
Lc.listsize = Lc.length = La.length + Lb.length;
ElemType *pc = Lc.elem = (ElemType *)malloc(Lc.listsize * sizeof(ElemType));
if(!Lc.elem) {
exit(OVERFLOW); //存储分配失败
}
pa_last = La.elem + La.length - 1;
pb_last = Lb.elem + Lb.length - 1;
while(pa <= pa_last && pb < pb_last) { //归并
if(*pa <= *pb) {
*pc = *pa;
pc++;
pa++;
} else {
*pc = *pb;
pc++;
pb++;
}
while(pa <= pa_last) { //插入La的剩余元素
*pc = *pa;
pc++;
pa++;
}
while (pb <= pb_last) { //插入Lb的剩余元素
*pc = *pb;
pc++;
pb++;
}// MergeList_Sq
}
}
这里应该不是很难,细心看就能看懂,而pa,pb,pc都是指针变量。
至此,第二章的前半部分就到这里了。
由于这些内容已经够多的了,而链表我觉得更是一个重量级,所以我决定再开一篇文章讲。