数据结构期末复习(第二章 线性表)
文章目录
感觉数据结构上手的时候,学这一章是超级痛苦
说在前面,知识点总结中的代码一定一定一定要手敲一遍,过程中一定会遇到各种各样的问题,只看不动手很容易眼高手低!
还有,代码都是伪代码,提供思路用的,不要照着抄
原理之类的看不懂,其实敲一遍就理解了,实践出真知。
Part 1、知识点总结
- 线性结构是最常用、最简单的一种数据结构。而线性表是一种典型的线性结构。其基本特点是线性表中的数据元素是有序且是有限的。在这种结构中:
① 存在一个唯一的被称为“第一个”的数据元素;
② 存在一个唯一的被称为“最后一个”的数据元素;
③ 除第一个元素外,每个元素均有唯一一个直接前驱;
④ 除最后一个元素外,每个元素均有唯一一个直接后继。
1.1线性表的定义
- 通常,定义线性表为n个数据元素(或称为表元)的有限序列。记为L = {a1,a2…an}。期中,L是表名,a是表中的结点,是不可再分割的数据。n是表中表元的个数,也称为表的长度,n=0的线性表叫做空表。
1.2线性表的特点
在非空的数据元素集合中,线性表的特点如下:
- 存在唯一的一个称作“第一个”的元素;
- 存在唯一的称作“最后一个”的元素
- 除第一个元素外,集合中的每个元素均只有一个直接前驱
- 除最后一个元素外,集合中的每个元素均只有一个直接后继。
最后两个特点体现了线性表中元素之间的逻辑关系
1.3线性表的逻辑结构
线性表中的数据元素ai所代表的具体含义随具体应用的不同而不同,在线性表的定义中,只不过是一个抽象的表示符号。
- 线性表中的结点可以是单值元素(每个元素只有一个数据项) 。
- 线性表中的结点可以是记录型元素,每个元素含有多个数据项 ,每个项称为结点的一个域 。每个元素有一个可以唯一标识每个结点的数据项组,称为关键字。
- 若线性表中的结点是按值(或按关键字值)由小到大(或由大到小)排列的,称线性表是有序的。
- 线性表是一种相当灵活的数据结构,其长度可根据需要增长或缩短。
- 对线性表的数据元素可以访问、插入和删除。
1.4 线性表的要点
- 表中元素具有逻辑上的顺序性,在序列中各元素的排列有其先后次序。各个数据元素在线性表中的逻辑位置只取决于其序号(或叫下标)
- 表中元素个数有限
- 表中元素都是数据元素。就是说,每一个表元素都是不可再分的原子数据。反之,如果表元素是表或其他结构,则不再是线性表
- 表中元素的数据类型都相同。这意味着每一个表元素占有相同数量的存储空间。
- 表中元素具有抽象性。就是说,我们仅仅讨论表元素之间的逻辑关系,不考虑元素究竟表示什么内容。
1.5 线性表的操作
- 表的初始化运算:将线性表置为空表
- 求表长度运算
- 查找运算:查找线性表中第i个元素或查找表中具有给定关键字的表元素
- 插入运算:将新表元素插入到线性表第i个位置上,或插入到具有给定关键字值的表元素的前面或后面
- 删除运算:删除线性表的第i个元素或具有给定关键字值的表元素
- 读取运算:读取线性表第i个表元素的值
- 复制运算:复制线性表的所有元素到另一个线性表中
2.1 线性表的顺序存储表示
本质上就是逻辑上连续存放的数据元素存储在物理上连续存放的存储单元中。
PPT上的语言如下:
-
设线性表的每个元素需占用l个存储单元,以所占的第一个单元的存储地址作为数据元素的存储位置。则线性表中第i+1个数据元素的存储位置LOC(ai+1)和第i个数据元素的存储位置LOC(ai)之间满足下列关系:
LOC(ai+1)=LOC(ai)+l
线性表的第i个数据元素ai的存储位置为:
LOC(ai)=LOC(a1)+(i-1)*l -
在高级语言(如C语言)环境下:数组具有随机存取的特性,因此,借助数组来描述顺序表。除了用数组来存储线性表的元素之外,顺序表还应该有表示线性表的长度属性,所以用结构类型来定义顺序表类型。
说人话其实就是:结构体来定义数据元素,然后存储在相邻的存储单元中。为了方便,通常采用指针对其进行操作。
注意:存储时有静态和动态两种存储方式
静态就是定义的结构体中元素用数组存储,但是如果空儿不够了就gg
动态就是利用malloc分配内存存储数据,空儿不够了可以加
2.1.1 初始化
其实就是给线性表分配一块物理上连续的存储单元,并初始化线性表的长度为0;
2.1.2插入
(1) 将线性表L中的第i个至第n个结点后移一个位置。
(2) 将结点e插入到结点ai-1之后。
(3) 线性表长度加1。
注意:要考虑线性表溢出、下标错误等问题
平均时间复杂度:O(n)
2.1.3顺序线性表的删除
(1) 将线性表L中的第i+1个至第n个结点依此向前移动一个位置。
(2) 线性表长度减1。
注意:考虑表空以及下标错误的情况
平均时间复杂度:O(n)
2.1.3 顺序线性表的查找定位删除
(1) 在线性表L查找值为x的第一个数据元素。
(2) 将从找到的位置至最后一个结点依次向前移动一个位置。
(3) 线性表长度减1。
平均时间复杂度:O(n)
2.2 线性表的链式存储表示
链式存储 :用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表简称线性链表。
存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
链表中结点的逻辑顺序和物理顺序不一定相同。
链表的优点:不要求大量连续空间,改变容量方便,但是需要空间来存放指针。
2.2.1结点的描述与实现
C语言中用带指针的结构体类型来描述
typedef struct Lnode
{ ElemType data; /*数据域,保存结点的值 */
struct Lnode *next; /*指针域*/
}LNode; /*结点的类型 */
插一句,typedef的作用是数据类型重命名
typedef <数据类型> <别名>
2.2.2 结点的实现
结点是通过动态分配和释放来的实现,即需要时分配,不需要时释放。实现时是分别使用C语言提供的标准函数:malloc() ,realloc(),sizeof() ,free() 。
动态分配 p=(LNode*)malloc(sizeof(LNode));
函数malloc分配了一个类型为LNode的结点变量的空间,并将其首地址放入指针变量p中。
动态释放 free(p);
系统回收由指针变量p所指向的内存区。P必须是最近一次调用malloc函数时的返回值。
2.2.3 关于指针
得会指针一系列指来指去的操作,针不戳~
比如下图这一堆 (= v =)
2.2.4 单线性链式的基本操作(单链表)
2.2.4.1建立单链表
动态地建立单链表的常用方法有如下两种:头插入法,尾插入法。
- 头插入法建表
从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。即每次插入的结点都作为链表的第一个结点。
LNode *create_LinkList(void)
/* 头插入法创建单链表,链表的头结点head作为返回值 */
{ int data ;
LNode *head, *p;
head= (LNode *) malloc( sizeof(LNode));
head->next=NULL; /* 创建链表的表头结点head */
while (1)
{ scanf(“%d”, &data) ;
if (data==32767) break ;
p= (LNode *)malloc(sizeof(LNode));
p–>data=data; /* 数据域赋值 */
p–>next=head–>next ; head–>next=p ;
/* 钩链,新创建的结点总是作为第一个结点 */
}
return (head);
}
- 头插入法建立链表虽然算法简单,但生成的链表中结点的次序和输入的顺序相反。若希望二者次序一致,可采用尾插法建表。该方法是将新结点插入到当前链表的表尾,使其成为当前链表的尾结点。
LNode *create_LinkList(void)
/* 尾插入法创建单链表,链表的头结点head作为返回值 */
{ int data ;
LNode *head, *p, *q;
head=p=(LNode *)malloc(sizeof(LNode));
p->next=NULL; /* 创建单链表的表头结点head */
while (1)
{ scanf(“%d”,& data);
if (data==32767) break ;
q= (LNode *)malloc(sizeof(LNode));
q–>data=data; /* 数据域赋值 */
q–>next=p–>next; p–>next=q; p=q ;
/*钩链,新创建的结点总是作为最后一个结点*/
}
return (head);
}
头插法就是从头往里塞,输入的时候倒着输入
尾插法就是从后面插入,输入的时候正常顺序输入就行。
无论是哪种插入方式,时间复杂度都是O(n)
2.2.4.2 单链表的查找
- 按序号查找 取单链表中的第i个元素。
对于单链表,不能象顺序表中那样直接按序号i访问结点,而只能从链表的头结点出发,沿链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。
ElemType Get_Elem(LNode *L , int i)
{ int j ; LNode *p;
p=L->next; j=1; /* 使p指向第一个结点 */
while (p!=NULL && j<i)
{ p=p–>next; j++; } /* 移动指针p , j计数 */
if (j!=i) return(-32768) ;
else return(p->data);
/* p为NULL 表示i太大; j>i表示i为0 */
}
时间复杂度:O(n)
- 按值查找
按值查找是在链表中,查找是否有结点值等于给定值key的结点? 若有,则返回首次找到的值为key的结点的存储位置;否则返回NULL。查找时从开始结点出发,沿链表逐个将结点的值和给定值key作比较。
LNode *Locate_Node(LNode *L,int key)
/* 在以L为头结点的单链表中查找值为key的第一个结点 */
{ LNode *p=L–>next;
while ( p!=NULL&& p–>data!=key) p=p–>next;
if (p–>data==key) return p;
else
{ printf(“所要查找的结点不存在!!\n”);
retutn(NULL);
}
}
时间复杂度:O(n)
2.2.4.3单链表的插入
插入运算是将值为e的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。因此,必须首先找到ai-1所在的结点p,然后生成一个数据域为e的新结点q,q结点作为p的直接后继结点。
void Insert_LNode(LNode *L,int i,ElemType e)
/* 在以L为头结点的单链表的第i个位置插入值为e的结点 */
{ int j=0; LNode *p,*q;
p=L–>next ;
while ( p!=NULL&& j<i-1)
{ p=p–>next; j++; }
if (j!=i-1) printf(“i太大或i为0!!\n ”);
else
{ q=(LNode *)malloc(sizeof(LNode));
q–>data=e; q–>next=p–>next;
p–>next=q;
}
}
时间复杂度:O(n)
2.2.4.4 单链表的删除
- 按序号删除
删除单链表中的第i个结点。
为了删除第i个结点ai,必须找到结点的存储地址。该存储地址是在其直接前趋结点ai-1的next域中,因此,必须首先找到ai-1的存储位置p,然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间,将其归还给“存储池”。
设单链表长度为n,则删去第i个结点仅当1≦i≦n时是合法的。则当i=n+1时,虽然被删结点不存在,但其前趋结点却存在,是终端结点。故判断条件之一是p–>next!=NULL。显然此算法的时间复杂度也是O(n)
void Delete_LinkList(LNode *L, int i)
/* 删除以L为头结点的单链表中的第i个结点 */
{ int j=1; LNode *p,*q;
p=L; q=L->next;
while ( p->next!=NULL&& j<i)
{ p=q; q=q–>next; j++; }
if (j!=i) printf(“i太大或i为0!!\n ”);
else
{ p–>next=q–>next; free(q); }
}
- 按值删除
删除单链表中值为key的第一个结点。
与按值查找相类似,首先要查找值为key的结点是否存在? 若存在,则删除;否则返回NULL。
void Delete_LinkList(LNode *L,int key)
/* 删除以L为头结点的单链表中值为key的第一个结点 */
{ LNode *p=L, *q=L–>next;
while ( q!=NULL&& q–>data!=key)
{ p=q; q=q–>next; }
if (q–>data==key)
{ p->next=q->next; free(q); }
else
printf(“所要删除的结点不存在!!\n”);
}
算法的执行与形参key有关,平均时间复杂度为O(n)。
变形之一:
删除单链表中值为key的所有结点。
与按值查找相类似,但比前面的算法更简单。
基本思想:
从单链表的第一个结点开始,对每个结点进行检查,若结点的值为key,则删除之,然后检查下一个结点,直到所有的结点都检查。
void Delete_LinkList_Node(LNode *L,int key)
/* 删除以L为头结点的单链表中值为key的第一个结点 */
{ LNode *p=L, *q=L–>next;
while ( q!=NULL)
{ if (q–>data==key)
{ p->next=q->next; free(q); q=p->next; }
else
{ p=q; q=q–>next; }
}
}
变形之二:
删除单链表中所有值重复的结点,使得所有结点的值都不相同。
与按值查找相类似,但比前面的算法更复杂。
基本思想:
从单链表的第一个结点开始,对每个结点进行检查:检查链表中该结点的所有后继结点,只要有值和该结点的值相同,则删除之;然后检查下一个结点,直到所有的结点都检查。
void Delete_Node_value(LNode *L)
/* 删除以L为头结点的单链表中所有值相同的结点 */
{ LNode *p=L->next, *q, *ptr;
while ( p!=NULL) /* 检查链表中所有结点 */
{ *q=p, *ptr=p–>next;
/* 检查结点p的所有后继结点ptr */
while (ptr!=NULL)
{ if (ptr–>data==p->data)
{ q->next=ptr->next; free(ptr);
ptr=q->next; }
else { q=ptr; ptr=ptr–>next; }
}
p=p->next ;
}
}
2.2.4.5 单链表的合并
设有两个有序的单链表,它们的头指针分别是La 、 Lb,将它们合并为以Lc为头指针的有序链表。
LNode *Merge_LinkList(LNode *La, LNode *Lb)
/* 合并以La, Lb为头结点的两个有序单链表 */
{ LNode *Lc, *pa , *pb , *pc, *ptr ;
Lc=La ; pc=La ; pa=La->next ; pb=Lb->next ;
while (pa!=NULL && pb!=NULL)
{ if (pa->data<pb->data)
{ pc->next=pa ; pc=pa ; pa=pa->next ; }
/* 将pa所指的结点合并,pa指向下一个结点 */
if (pa->data>pb->data)
{ pc->next=pb ; pc=pb ; pb=pb->next ; }
/* 将pa所指的结点合并,pa指向下一个结点 */
if (pa->data==pb->data)
{ pc->next=pa ; pc=pa ; pa=pa->next ;
ptr=pb ; pb=pb->next ; free(ptr) ; }
/* 将pa所指的结点合并,pb所指结点删除 */
}
if (pa!=NULL) pc->next=pa ;
else pc->next=pb ; /*将剩余的结点链上*/
free(Lb) ;
return(Lc) ;
}
若La ,Lb两个链表的长度分别是m,n,则链表合并的时间复杂度为O(m+n) 。
2.3 循环链表
循环链表(Circular Linked List):是一种头尾相接的链表。其特点是最后一个结点的指针域指向链表的头结点,整个链表的指针域链接成一个环。
从循环链表的任意一个结点出发都可以找到链表中的其它结点,使得表处理更加方便灵活。
循环链表的操作
对于单循环链表,除链表的合并外,其它的操作和单线性链表基本上一致,仅仅需要在单线性链表操作算法基础上作以下简单修改:
⑴ 判断是否是空链表:head->next == head ;
⑵ 判断是否是表尾结点:p->next==head ;
2.4 双向链表
双向链表(Double Linked List) :指的是构成链表的每个结点中设立两个指针域:一个指向其直接前趋的指针域prior,一个指向其直接后继的指针域next。这样形成的链表中有两个方向不同的链,故称为双向链表。
和单链表类似,双向链表一般增加头指针也能使双链表上的某些运算变得方便。
将头结点和尾结点链接起来也能构成循环链表,并称之为双向循环链表。
双向链表是为了克服单链表的单向性的缺陷而引入的。
本质上,双向链表就是多了个prior的指针域,存储往前指的指针罢了。
2.4.1 双向链表的操作
定义结点时,无非就是多定义一个指针域
typedef struct Dulnode
{ ElemType data ;
struct Dulnode *prior , *next ;
}DulNode ;
插入:
① 插入时仅仅指出直接前驱结点,钩链时必须注意先后次序是: “先右后左” 。部分语句组如下:
S=(DulNode *)malloc(sizeof(DulNode));
S->data=e;
S->next=p->next; p->next->prior=S;
p->next=S; S->prior=p; /* 钩链次序非常重要 */
② 插入时同时指出直接前驱结点p和直接后继结点q,钩链时无须注意先后次序。部分语句组如下:
S=(DulNode *)malloc(sizeof(DulNode));
S->data=e;
p->next=S; S->next=q;
S->prior=p; q->prior=S;
删除:
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);
2.3 线性表存储结构的要点
- 顺序表的表元素是相继存放的,元素中间没有间隔,不像一维数组,可以按下标直接存放,元素之间可以有间隔;
- 单链表从表头指针开始,而最后一个结点的指针为空表示收尾;链表为空则表头指针为空,如果特别设置附加表头结点,则链表为空还需保留头结点。
- 从访问方式来看,顺序表可以从前向后顺序存取,也可以从后向前顺序存取,还可以直接存取(注意它与一维数组的区别);单链表只能从链头顺序存取。
- 从表头元素的逻辑顺序与物理位置的对应关系来看,顺序表中表元素的逻辑顺序与它们的物理存放顺序完全一致;而单链表中各个表元素的物理存储位置与逻辑顺序不一定一致,元素在逻辑上的下一个通过相应结点的链接指针指示。
- 从存储空间的利用率来看,若定义存储密度 = 表中数据元素占有的空间/分配给表的总空间,则顺序表的存储密度为1,因为表示数据之间的逻辑关系无需占用附加空间;而单链表的存储密度小于1,因为每个数据都需附加一个链接指针以指示元素之间的逻辑关系。
- 从查找速度来看,单链表只能沿链逐个比较,顺序访问,不能随机访问;而顺序表可以按照元素序号的下标直接访问,故顺序表的查找速度比线性链表要快。
- 从插入和删除速度来看,如果要求插入和删除后表中其他元素的相对逻辑顺序保持不变,则顺序表平均需要移动大约一般元素,而单链表只需修改链接指针,不需要移动元素,因此线性链表比顺序表的插入和删除速度快。
- 从C指针的使用来看,使用顺序表存储,指针p指示数据元素的存储位置,用p可取得该点数据的值,用p++可以顺序指向物理上下一个数据元素的位置;使用单链表存储,指针p指示链表结点的地址,用p不能取得该结点数据的值,用p++也不能指向下一个结点的位置,只能使用p->data取得结点数据的值,用p = p->link指向下一个结点
- 从空间限制来看,顺序表在静态存储分配的情形下,一旦存储空间装满不能扩充,如果要再加入新元素将出现存储溢出;在动态存储分配的情形下虽然存储可以扩充,但需要移动大量元素,将导致操作效率降低。单链表的结点空间只有在需要的时候才申请,无需事先分配,因此,只要内存还有空间可分配,就没有存储溢出问题,插入和删除操作效率也优于顺序表。
PPT课后题
1 简述下列术语:线性表,顺序表,链表。
2 何时选用顺序表,何时选用链表作为线性表的存储结构合适?各自的主要优缺点是什么?
3 在顺序表中插入和删除一个结点平均需要移动多少个结点?具体的移动次数取决于哪两个因素?
4 链表所表示的元素是否有序?如有序,则有序性体现于何处?链表所表示的元素是否一定要在物理上是相邻的?有序表的有序性又如何理解?
5 设顺序表L是递增有序表,试写一算法,将x插入到L中并使L仍是递增有序表。
6 写一求单链表的结点数目ListLength(L)的算法。
7 写一算法将单链表中值重复的结点删除,使所得的结果链表中所有结点的值均不相同。
8 写一算法从一给定的向量A删除值在x到y(x≤y)之间的所有元素(注意:x和y是给定的参数,可以和表中的元素相同,也可以不同)。
9 设A和B是两个按元素值递增有序的单链表,写一算法将A和B归并为按按元素值递减有序的单链表C,试分析算法的时间复杂度。
自己康的一点题
- 单链表中,增加头结点的目的是为了方便运算的实现
- 在顺序表中,只要知道基地址和结点大小,就可在相同时间内求出任一结点的存储地址。
- 对顺序表上的插入、删除算法的时间复杂性分析来说,常以结点移动为标准操作
- 数据在计算机存储器内表示时,物理地址与逻辑地址不相同的,称为链式存储结构
- 对循环链表来说,从表中任一结点出发,都能通过前后操作扫描整个循环链表是错误的
- 在等概率情况下,顺序表的插入操作要移动一半结点。
- 线性表是一个有限序列,可以为空
- 在顺序存储结构中,有时也存储数据结构中元素之间的关系(X)
- 线性表中的每一个表元素都是数据对象,它们是不可再分的数据元素
- 除数组外,每种数据结构都应具备三种基本运算:插入、删除和查找
- 顺序表是线性表的数组存储表示
- 单链表是一种非顺序存储线性表
- 若长度为n的非空线性表采用顺序存储结构,在表的第i个位置插入一个数据元素,i的合法值是 1 <= i <= n+1
- 链接存储的特点是利用引用来表示数据元素之间的逻辑关系
Part 2、代码
都是一些很早之前写的线性表/链表相关的代码了,有的地方可能很low,仅供参考;
#include "stdio.h"
#include "stdlib.h"
#define error -1
#define ok 1
typedef struct LNode{
int data;
struct LNode *next;
}LNode;
void create_list(LNode *L)
{
printf("Please input numbers until '9999' is inputed.\n");
int x;
L->next=NULL;
LNode *s,*r;
s=L;
scanf("%d",&x);
while(x!=9999)
{
r=(LNode*)malloc(sizeof(LNode));
r->data=x;
s->next=r;
s=r;
scanf("%d",&x);
}
s->next=NULL;
}
void print(LNode *L)
{
LNode *p;
p=L->next;
while(p)
{
printf("%d ",p->data);
p=p->next;
}
}
int insert_list(LNode *L,int n,int e)
{
if(n<0) return error;
LNode *p,*r,*s;
r=L;
int i;
p=(LNode*)malloc(sizeof(LNode));
p->data=e;
for(i=0;i<n-1&&r!=NULL;i++)
{
r=r->next;
}
s=r->next;
r->next=p;
p->next=s;
return ok;
}
int length_list(LNode *L)
{
LNode *p;
int len=0;
p=L->next;
while(p)
{
p=p->next;
len++;
}
return len;
}
int sort_list(LNode *L,int length)
{
int len=length,i;
LNode *p,*s;
int tmp;
p=L->next;
while(len>1)
{
for(i=0;i<len-1;i++,p=p->next)
{
s=p->next;
if(p->data>s->data)
{
tmp=p->data;
p->data=s->data;
s->data=tmp;
}
}
p=L->next;
len--;
}
return ok;
}
int main()
{
int n,e,tmp;
LNode L;
printf("0->Quit the program;\n");
printf("1->Create a new list;\n");
printf("2->Insert a new number;\n");
printf("3->Sort the list;\n");
printf("4->Print the list;\n");
printf("5->Get the length of list;\n");
printf("Please input a number:");
int number,len;
while(1)
{
scanf("%d",&number);
if(number==0) {
printf("Thanks for using!\n");
break;
}
if(number==1)
{
create_list(&L);
len=length_list(&L);
printf("The list has been created successfully!\n");
continue;
}
if(number==2)
{
printf("Please input the position and number:\n");
scanf("%d %d",&n,&e);
tmp=insert_list(&L,n,e);
if(tmp==error)
printf("Failed to insert!Wrong data!\n");
else
printf("Inserted successfully!\n");
continue;
}
if(number==3)
{
len=length_list(&L);
sort_list(&L,len);
printf("Sorted successfully!\n");
continue;
}
if(number==4)
{
print(&L);
printf("\n");
continue;
}
if(number==5)
{
len=length_list(&L);
printf("The length of list is %d\n",len);
continue;
}
}
return 0;
}
#include "stdio.h"
#include "stdlib.h"
#define error -1
#define ok 1
typedef struct LNode{
char data;
struct LNode *next;
}LNode;
void create_list(LNode *L)
{
printf("Please input numbers until 'ENTER' is inputed.\n");
int x;
L->next=NULL;
LNode *s,*r;
s=L;
scanf("%c",&x);
while(1)
{
r=(LNode*)malloc(sizeof(LNode));
r->data=x;
s->next=r;
s=r;
EX:
scanf("%c",&x);
if(x==' ') goto EX;
if(x=='\n') break;
}
s->next=NULL;
}
void print(LNode *L)
{
LNode *p;
p=L->next;
while(p)
{
printf("%c ",p->data);
p=p->next;
}
}
void Separate(LNode *L,LNode *l1,LNode *l2,LNode *l3)
{
l1->next=NULL;
l2->next=NULL;
l3->next=NULL;
LNode *p,*r1,*r2,*r3,*s1,*s2,*s3;
p=L;
s1=l1;
s2=l2;
s3=l3;
int i;
while(p)
{
if(p->data>='0'&&p->data<='9')
{
r1=(LNode*)malloc(sizeof(LNode));
r1->data=p->data;
s1->next=r1;
s1=r1;
s1->next=NULL;
}
else if(p->data>='a'&&p->data<='z')
{
r2=(LNode*)malloc(sizeof(LNode));
r2->data=p->data;
s2->next=r2;
s2=r2;
s2->next=NULL;
}
else if(p->data>='A'&&p->data<='Z')
{
r3=(LNode*)malloc(sizeof(LNode));
r3->data=p->data;
s3->next=r3;
s3=r3;
s3->next=NULL;
}
p=p->next;
}
}
void create(LNode *l)
{
l->next=NULL;
}
int main()
{
int n,e,tmp;
LNode L,l1,l2,l3;
create(&l1);
create(&l2);
create(&l3);
printf("0->Quit the program;\n");
printf("1->Create a new list;\n");
printf("2->Separate the list;\n");
printf("3->Print L1;\n");
printf("4->Print L2;\n");
printf("5->Print L3;\n");
printf("6->Print List;\n");
while(1)
{
printf("Please input a number:\n");
scanf("%d",&n);
getchar();
if(n==0)
{
printf("Thanks for using!\n");
break;
}
if(n==1)
{
create_list(&L);
printf("The list has been created successfully!\n");
continue;
}
if(n==2)
{
Separate(&L,&l1,&l2,&l3);
printf("The list has been separated successfully!\n");
continue;
}
if(n==3)
{
printf("The list1 is:\n");
print(&l1);
printf("\n");
continue;
}
if(n==4)
{
printf("The list2 is:\n");
print(&l2);
printf("\n");
continue;
}
if(n==5)
{
printf("The list3 is:\n");
print(&l3);
printf("\n");
continue;
}
if(n==6)
{
printf("The list is:\n");
print(&L);
printf("\n");
continue;
}
}
return 0;
}
#include "stdio.h"
#include "stdlib.h"
#define error -1
#define ok 1
typedef struct DNode{
int data;
struct DNode *next,*prior;
}DNode;
void create_list(DNode *L)
{
L->next=NULL;
L->prior=NULL;
int x;
DNode *p,*s;
s=L;
scanf("%d",&x);
while(x!=9999)
{
p=(DNode*)malloc(sizeof(DNode));
p->data=x;
s->next=p;
p->prior=s;
p->next=NULL;
s=p;
scanf("%d",&x);
}
p->next=L;
}
int length(DNode *l)
{
int len=0;
DNode *p;
p=l->next;
while(p)
{
len++;
p=p->next;
}
return len;
}
int check(DNode *l,int len)
{
int tmp,i;
DNode *s,*r;
s=l;
if(len%2==0)
{
tmp=len/2;
for(i=0;i<tmp;i++)
{
s=s->next;
}
r=s->next;
for(i=0;i<tmp;i++)
{
if(s->data==r->data)
continue;
else return error;
s=s->prior;
r=r->next;
}
return ok;
}
if(len%2==1)
{
tmp=len/2+1;
for(i=0;i<tmp-1;i++)
{
s=s->next;
}
r=s->next->next;
for(i=0;i<tmp;i++)
{
if(s->data==r->data)
continue;
else return error;
s=s->prior;
r=r->next;
}
return ok;
}
}
void print(DNode *l)
{
DNode *p;
p=l->next;
while(p)
{
printf("%d ",p->data);
p=p->next;
}
}
int main()
{
DNode l;
create_list(&l);
// print(&l);
int len,answer;
len=length(&l);
answer=check(&l,len);
if(answer==ok) printf("yes");
else printf("no");
return 0;
}
Part 3、总结
15000字…累死我了…
题目我只选了一些我觉得需要背/理解的,一些比较灵活的题不是很方表敲出来。
重难点是理解内存和指针吧…一定要把代码自己敲一遍,至少带有初始化、插入删除打印合并的顺序表和单链表得能自己独立敲出来
然后就是要会算各个步骤的时间复杂度
和C语言入门不一样,数据结构一味死记不理解的话很多比较灵活的题是做不出来的。
还有就是要搞懂形参和实参
避免出现指针漂移类似的错误(因为我都经历过了)
malloc分配内存的时候,一定要理解,是在系统中给你又分配了一块,然后让你指针往那儿指,所以你指针原来指向的空间就没有发生变化。不要在函数里操作半天结果主函数里无事发生
单链表一些复杂的操作如果空间想象力不够建议画出来,指一指