广大考研数据结构复习之单链表
王道书籍上单链表的基本操作和课后部分习题
一:头文件的定义
#ifndef LINKLIST_H #define LINKLIST_H /** * @author Dawn * @date 2019年11月7日19:57:24 * @version 1.0 * 数据结构二轮复习之顺序表,不要问我为什么时间不对劲,电脑的机械硬盘坏了,晕,所有东西都没了,现在都在重写 */ #include<stdio.h> #include<stdlib.h> #include<time.h> typedef int ElemType; typedef struct LNode { ElemType data;//数据域 struct LNode* next; }LNode, * LinkList; //循环双链表,仅仅是为了17题写的,没有初始化,懒得写那些了,运行不出来的!! typedef struct DSNode { ElemType data; struct DSNode* next; struct DSNode* prior; }DSNode,*DSLinkList; //==========================单链表的基本操作============================== //1.头插法建立单链表(带头结点,没有特殊说明默认是带头节点的) LinkList List_Insert_Head(LinkList& L); LinkList List_Insert_NoHead(LinkList& L);//(不带头节点的) //2.尾插法建立单链表 LinkList List_Insert_Tail(LinkList& L); //3.按序号查找节点 LNode *GetElem(LinkList L, int i); //4.按值查找节点 LNode* LocateElem(LinkList L, ElemType e); //==========================课后习题====================================== //1.使用递归,删除不带头节点的单链表L中所有值为x的节点 void Del_X1(LinkList& L, ElemType e); //2.在带头节点的单链表L中,删除L中所有值为x的节点 void Del_X2(LinkList& L, ElemType e); //3.从尾到头输出带头结点的单链表L中的所有节点值; void R_Print3(LinkList L); //4.删除带头结点的单链表L中最小值的节点高效方法(该最小值节点唯一) void Del_Min4(LinkList& L); //5.带头结点的单链表逆置 void Reverse5(LinkList& L); //6.使带头节点的单链表有序 void Sort_LinkList6(LinkList& L); //7.删除无序带头节点的单链表L中值在(min,max)之间的节点 void Del_range(LinkList& L, int min, int max); //8.给2个单链表,找出2个单链表的公共节点 LNode* Find_SameNode(LinkList A, LinkList B); //9.给定一个带头节点的单链表,递增输出各个节点的值,并释放节点空间 //思路?先找到单链表中元素最小的值,然后就删除该节点 void Del_Mins(LinkList& L); //10.将单链表A分为2个单链表A,B。A中存原表中序号为奇数的节点,B中存原表中序号为偶数的节点。且保持相对位置不变 LinkList Split2LinkList10(LinkList& A); //11.将单链表A{a1,b1,a2,b2,a3,b3}分解为A{a1,a2,a3} B{b3,b2,b1}。此题和上一题要求差不多只不过B中元素逆置了.这里B中就用头插法来实现,A中还是用尾插法完成 LinkList Split2LinkList11(LinkList& A); //12.在有序的单链表中,删除值相同的节点(记得保留一个), void Del_Same(LinkList& L); //13.将2个有序递增的单链表AB合并成一个有序递减的单链表A void MergeList(LinkList& A, LinkList& B); //14.将2个有序单链表中的公共值节点拼成一个新的链表C。不要删除原来节点的值 LinkList Get_Common(LinkList A, LinkList B); //15.求2个有序链表的并集, LinkList Union(LinkList& la, LinkList& lb); //16.判断一个链表是否是另一个链表的子链表,也就是字符串判断他的子串 好像是Java中的Contains函数??唉!!为了复习考研我竟然有大半年没有写Java了。 bool Pattern(LinkList A, LinkList B); //17.判断一个循环双链表是否对称 bool Symmetry(DSLinkList L); //18.将2个循环单链表合并成一个循环单链表,保持相对位置不变 LinkList Link(LinkList& h1, LinkList& h2); //19.依次输出循环单链表中的较小值。并删除最小值节点 void Del_MinAll(LinkList& L); //==========================广大915真题篇============================== //2019第二题,要求:将一个带头节点的单链表L中值重复的多余节点删除,(就是去重) void DelDistincts(LinkList& L); //==========================常用函数============================== void PrintList(LinkList L); void PrintList_NoHead(LinkList L); #endif // !LINKLIST_H
二:头文件具体的实现
#include"LinkList.h" //==========================单链表的基本操作============================== //1.头插法建立单链表(带头结点,没有特殊说明默认是带头节点的) LinkList List_Insert_Head(LinkList& L) { int arr[6] = { 1,2,3,4,5,6 };//将改数组插入到单链表中 LNode* s;//待插入的节点 int i = 0; L = (LinkList)malloc(sizeof(LNode));//创建头节点 L->next = NULL; while (i < 6) { s = (LNode*)malloc(sizeof(LNode)); s->data = arr[i++]; s->next = L->next;//将L后的东西放在S后面,以避免丢失 L->next = s; } return L; } //(不带头节点的) LinkList List_Insert_NoHead(LinkList& L) { int arr[6] = { 1,2,3,4,5,6 };//将改数组插入到单链表中 LNode* s;//待插入的节点 int i = 0; while (i < 6) { if (i == 0) { L = (LinkList)malloc(sizeof(LNode)); L->data = arr[i++]; L->next = NULL; } else { s = (LNode*)malloc(sizeof(LNode)); s->data = arr[i++]; //s->next = L->next; //这样写出来的是1 6 5 4 3 2 //L->next = s; s->next = L; L = s; } } return L; } //2.尾插法建立单链表 LinkList List_Insert_Tail(LinkList& L) { //int arr[6] = { 1,2,3,4,5,6 };//将改数组插入到单链表中 int arr[6] = { 1,2,2,4,5,6 };//将改数组插入到单链表中 L = (LinkList)malloc(sizeof(LNode));//创建头节点 LNode* s, * r = L;//s为待插入的节点,r指向尾指针的 int i = 0; while (i < 6) { s = (LNode*)malloc(sizeof(LNode)); s->data = arr[i++]; r->next = s; r = s; } r->next = NULL;//最后尾指针指向NULL return L; } //3.按序号查找节点 //如果输入的是0,就返回头节点,小于1退出, LNode* GetElem(LinkList L, int i) { int j=1;//从1开始计数 LNode* p = L->next;//p为工作指针 if (i == 0) return L;//返回头节点 if (i < 1) return NULL; while (p && j < i) { p = p->next; j++; } return p;//注意这里p,因为循环里面写的p=p->next } //4.按值查找节点 LNode* LocateElem(LinkList L, ElemType e) { LNode* p = L->next; while (p != NULL && p->data != e) { p = p->next; } return p; } //==========================课后习题====================================== //1.使用递归,删除不带头节点的单链表L中所有值为x的节点 //递归条件:当改节点值是x就删除在递归调用该函数,不是该节点就调用该节点的的next。 //递归退出条件:递归到最后一个节点就没了 void Del_X1(LinkList &L, ElemType e) { LNode* q;//待删除的节点 if (L == NULL) return; if (L->data == e) { q = L; L = L->next; free(q);//释放该节点 Del_X1(L, e); } else { Del_X1(L->next, e); } } //2.在带头节点的单链表L中,删除L中所有值为x的节点 void Del_X2(LinkList& L, ElemType e) { LNode *p = L->next;//p工作指针 LNode* prep = L;//p的前驱 LNode* q;//待删除的节点 while (p) { //如果p是要删除的值 if (p->data == e) { q = p; p=p->next; prep->next = p;//不写这一句就要断链了 free(q); } else { prep = p; p = p->next; } } } //3.从尾到头输出带头结点的单链表L中的所有节点值; void R_Print3(LinkList L) { //使用递归 //if (L) { // R_Print3(L->next); //} //if(L) // printf("%d ,", L->data); if (L->next!=NULL) { R_Print3(L->next); } printf("%d ,", L->data); } //4.删除带头结点的单链表L中最小值的节点高效方法(该最小值节点唯一) void Del_Min4(LinkList& L) { //这个不是有序的,我还打算用二分法找最下值元素了,可以吗?? 哦哦,也不可以哈,顺序存储才得行哈!!发散思维到此结束 LNode* p = L->next, *prep = L;//p prep视为工作指针 LNode* minp = p, * prepmin = prep;//分别保存最下值的节点以及他的前驱 while (p) { if (p->data < minp->data) { //p小于minp minp = p; prepmin = prep; } prep = p; p = p->next; } //找到了最小值的节点以及他的前驱 prepmin->next = minp->next; free(minp); } //5.带头结点的单链表逆置 void Reverse5(LinkList& L) { //使用头插法的方法就行了 LNode* p = L->next, * r;//p为工作指针, r为插入之后的尾指针 L->next = NULL;//这里就将L->next置空 while (p != NULL) { r = p->next; p->next = L->next; L->next = p; p = r; } } //6.使带头节点的单链表有序(有点陌生,许多看) //使用了直接插入排序的思想,1:pre为有序部分的单链表遍历时的工作指针,遍历完一遍之后的结果是指向第一个大于待插入节点值的前驱元素, // 2:找到合适的pre之后,就插入p(无序部分的工作指针)的值,插入成功之后p后移,直到p遍历完成 void Sort_LinkList6(LinkList& L) { LNode* p=L->next, * pre;//p为无序断的工作指针,pre找到插入的合适位置的前驱 的工作指针 LNode* r = p->next;//保证p不断链 p->next = NULL; p = r;//执行第二个节点 while (p != NULL) { r = p->next; pre = L; //pre的后继有元素,pre后继元素的值小于p的值,pre就向下移 while (pre->next != NULL && pre->next->data < p->data) { pre = pre->next; } p->next = pre->next; pre->next = p; p = r;//扫描表中剩下的节点 } } //7.删除无序带头节点的单链表L中值在(min,max)之间的节点 void Del_range(LinkList& L, int min, int max) { //通过2个指针来实现 LNode* p = L->next, * pre = L;//p为工作指针,pre为它的前驱 LNode* q;//待删除的节点 while (p != NULL) { if (p->data > min&& p->data < max) { q = p; p = p->next; pre->next = p;//防止断链 free(q);//删除 } else { pre = p; p = p->next; } } } //8.给2个单链表,找出2个单链表的公共节点,就像一个躺着的Y //思路:先找到比较长的单链表,从左向右遍历,遍历到和短的单链表长度一样适合,在同时遍历2个单链表,每次遍历的时候就比较地址 /* 0x0001 ->next 0x0004 ->next \ 地址表示: ———— 0x0008 ->next 0x0012 ->next 0x0000 / 0x0002 ->next 0x0003 ->next 0x0005 ->next */ LNode* Find_SameNode(LinkList A, LinkList B) { //这里就直接赋值A B的长度了,没有写length(A)函数, int Alen = 4, Blen = 5;//这里按照上面图来赋值的 LinkList longList, shortList;//分别指向较长的单链表和较短的单链表 int dist;//2个链表长度差 if (Alen > Blen) { longList = A->next; shortList = B->next; dist = longList - shortList; } else { longList = B->next; shortList = A->next; dist = longList - shortList; } while (dist--) longList = longList->next;//后移dist(AB长度差),使之同步 //开始寻找公共节点 while (longList != NULL) { if (longList == shortList)//比较的是地址 return longList;//返回二者之一均可 else { longList = longList->next; shortList = shortList->next; }//if结束 } return NULL; } //9.给定一个带头节点的单链表,递增输出各个节点的值,并释放节点空间 //思路?先找到单链表中元素最小的值,然后就删除该节点。(这里注意的是在找最小值节点的时候,每次都是从第一个节点开始找) void Del_Mins(LinkList& L) { LNode* p,* pre, * q;//p工作指针,pre为待删除节点的前驱,q为待删除的节点 while (L->next != NULL) { //每次都会少一个最小值的节点 pre = L; p = pre->next;//每次都从第二个元素开始寻找,第一个元素都使pre->next(也就是最小值的元素) while (p->next!=NULL) {//(助记:每次最开始的一次循环是:第二个元素和第一个元素比较大小) if (p->next->data < pre->next->data) { pre = p;//将最小值的前驱节点更新 } p = p->next; } //通过这个循环找到了最小值节点的前驱 q = pre->next; printf("%d, ", q->data); pre->next = q->next;//删除了该节点 free(q); } //最后释放头节点 free(L); } //10.将单链表A分为2个单链表A,B。A中存原表中序号为奇数的节点,B中存原表中序号为偶数的节点。且保持相对位置不变 LinkList Split2LinkList10(LinkList& A) { int i = 0;//用来判断是奇数还是偶数位序的节点 //创建B链表 LinkList B = (LinkList)malloc(sizeof(LNode)); B->next = NULL; LNode* ra = A, * rb = B;//ra,rb分别是AB的尾节点,因为要保持先对位置不变,就使用尾插法 LNode* p = A->next;//p为工作指针 A->next = NULL;//A链表置空,以便插入。不要误以为A链表之后的东西就没了,其实p一直保存起来的了 while (p != NULL) { i++; if (i % 2 == 0) {//偶数插入B中 rb->next = p; rb = p; } else { ra->next = p; ra = p; } p = p->next; } //插入成功,再将A B 2链表的尾节点置空 ra->next = NULL; rb->next = NULL; return B; } //11.将单链表A{a1,b1,a2,b2,a3,b3}分解为A{a1,a2,a3} B{b3,b2,b1}。此题和上一题要求差不多只不过B中元素逆置了.这里B中就用头插法来实现,A中还是用尾插法完成 LinkList Split2LinkList11(LinkList& A) { LinkList B = (LinkList)malloc(sizeof(LNode)); B->next = NULL; LNode* p = A->next, * q;//p为工作指针 。当p节点插入到B中的时候,就用q来防止断链的 LNode* ra = A;//为A的尾节点 while (p != NULL) { ra->next = p; ra = p;//A进行尾插法 p = p->next; q = p->next;//即将插到B中,防止断链 p->next = B->next; B->next = p; p = q;//p插入成功之后,又接回到直接断链的位置 } //循环结束,A链表尾指针置NULL ra->next = NULL; return B; } //12.在有序的单链表中,删除值相同的节点(记得保留一个), void Del_Same(LinkList& L) { LNode* p = L->next, * q;//q为要删除的重复节点 if (p == NULL) return; while (p->next != NULL) { q = p->next;//同时q也是p的后继 if (p->data == q->data) { p->next = q->next; free(q); } else { p = p->next; } } } //13.将2个有序递增的单链表AB合并成一个有序递减的单链表A void MergeList(LinkList &A, LinkList &B) { LNode* pa = A->next, * pb = B->next;//分别是AB的工作指针 LNode* r;//防止断链的 A->next = NULL;//将A置空,以便插入元素到里面 while (pa && pb) {//当2链表均不为空时候,循环 if (pa->data <= pb->data) {//如果pa的元素小于pb,就将pa使用头插法插到A中 r = pa->next;//防止断链 pa->next = A->next; A->next = pa; pa = r;//恢复pa的工作指针性质 } else { r = pb->next; pb->next = A->next; A->next = pb; pb = r; } } //通常情况下有一个链表会有剩余, if (pa) pb = pa;//如果pa不为空,pb就执行pa,(默认将pb认为指向不为空的那个链表) while (pb) { r = pb->next; pb->next = A->next; A->next = pb; pb = r; } free(B); } //14.将2个有序单链表中的公共值节点拼成一个新的链表C。不要删除原来节点的值 //思路?step1:2个链表是有序的,可以从第一个元素一次比较A B2表中的元素。 // step2:如果元素值不相等,则值小的指针后移。 // step3:如果元素相等,创建一个值等于2节点的新节点,再使用尾插法插入的C中,最后2工作指针都后移一位 LinkList Get_Common(LinkList A, LinkList B) { LNode* p = A->next, * q = B->next;//p q分别为A.B的工作指针 LNode* r;//为C的尾节点,进行尾插法使用的 LNode* s;//为值相同的新节点 LinkList C = (LinkList)malloc(sizeof(LNode)); r = C; while (p != NULL && q != NULL) { if (p->data < q->data) { p = p->next; } else if (p->data > q->data) { q = q->next; } else { s = (LNode*)malloc(sizeof(LNode)); //将值相同的节点复制到s的数据域中 s->data = p->data; r->next = s; r = s; //p q同时后移 p = p->next; q = q->next; } } r->next = NULL; return C; } //15.求2个有序链表的并集, //思路:1:使用归并的方法,设置2个工作指针pa,pb,对2个链表进行归并并扫描, // 2:只有同时出现在2个集合中的元素链接到结果表中,且仅保留一个其他节点全部释放掉, // 3:当其中一个节点遍历完之后,释放另一个剩下表的全部节点 LinkList Union(LinkList& la, LinkList& lb) { LNode* pa = la->next; LNode* pb = lb->next;//工作指针pa pb LNode* u;//待删除的节点 LNode* pc = la;//结果表中当前合并节点的前驱指针(这里的结果表示la哦。不要搞错了) while (pa && pb) { if (pa->data == pb->data) { //交集放入到尾插到结果表中 pc->next = pa; pc = pa; pa = pa->next; u = pb;//删除B中的相同节点 pb = pb->next; free(u); } else if (pa->data < pb->data) { u = pa; pa = pa->next; free(u); } else { u = pb; pb = pb->next; free(u); } } while (pa) { u = pa; pa = pa->next; free(u); } while (pb) { u = pb; pb = pb->next; free(u); } pc->next = NULL;//尾插法的嘛。记得尾节点指向NULL free(lb);//记得删除lb,不是删除la,也不应该删除la,因为是把节点插入到la中的 return la; } //16.判断一个链表是否是另一个链表的子链表(没有头节点的),也就是字符串判断他的子串 好像是Java中的Contains函数??唉!!为了复习考研我竟然有大半年没有写Java了。 bool Pattern(LinkList A, LinkList B) { LNode* p = A; LNode* pre = p;//记录每趟比较链表中的开始节点 LNode* q = B; while (p && q) { if (p->data == q->data) { p = p->next; q = q->next; } else { pre = pre->next; p = pre;//p又为A链表中的新开始比较节点 q = B;//q从B链表第一个节点开始 } } if (q == NULL) return true;//q为空说明B就是A的子序列 else return false; } //17.判断一个循环双链表是否对称 bool Symmetry(DSLinkList L) { DSNode* p = L->next, * q = L->prior;//p从左向右遍历,q从右向左遍历 while (p != q && p->next != q) {//当q p不等,或者p q相邻就退出循环 if (p->data == q->data) { p = p->next; q = q->prior; } else { return false;//有一个不相同就是不对称的 } } return true; } //18.将2个循环单链表合并成一个循环单链表,保持相对位置不变 LinkList Link(LinkList& h1, LinkList& h2) { //step1:找到2个循环单链表的尾节点 LNode* p, * q; p = h1; q = h2; while (p->next != h1) p = p->next; while (q->next != h2) q = q->next; //step2:将h1的尾节点指向h2的头节点,h2的尾节点指向h1的头节点 p->next = h2; q->next = h1; return h1; } //19.依次输出循环单链表中的较小值。并删除最小值节点 void Del_MinAll(LinkList& L) { LNode* p, * pre, * minp, * minpre;//p为工作指针,pre为它的前驱,minp指向最小值的节点,minpre为它的前驱 while (L->next = L) {//循环单练表的嘛 p = L->next; pre = L; minp = p; minpre = pre; //找最小值 while (p != L) { if (p->data < minp->data) { minp = p; minpre = pre; } pre = p; p = p->next; } printf("%d, ", minp->data); minpre->next = minp->next;//删除该节点 free(minp); } free(L);//释放头节点 } //==========================广大915真题篇============================== //2019第二题,要求:将一个带头节点的单链表L中值重复的多余节点删除,(就是去重) void DelDistincts(LinkList& L) { LNode* p, * pre, * q;//p为工作指针,pre为它的前驱,q为待删除的节点 pre = L; p = L->next; while (p) { if (p->data == pre->data) { q = p; p = p->next; pre->next = p; free(q); } else { pre = p; p = p->next;//如果pre和p不同,则同时后移 } } } //==========================常用函数============================== void PrintList(LinkList L) { LNode* p = L->next; while (p) { printf("%d ,", p->data); p = p->next; } printf("\n"); } void PrintList_NoHead(LinkList L) { while (L) { printf("%d ,", L->data); L = L->next; } printf("\n"); }
三:主函数如下
#include"LinkList.h" int main() { LinkList L; LNode* temp; //==========================单链表的基本操作============================== //1.头插法 //List_Insert_Head(L); //List_Insert_NoHead(L); //2.尾插法 List_Insert_Tail(L); //3.按序号查找节点 //temp =GetElem(L, 3); //printf("%d\n", temp->data); //4.按值查找节点 //LocateElem(L, 3); //==========================课后习题====================================== //1.使用递归,删除不带头节点的单链表L中所有值为x的节点(测试记得使用头插法不带头节点的函数) //Del_X1(L, 3); //2.在带头节点的单链表L中,删除L中所有值为x的节点 //Del_X2(L, 3); //3.从尾到头输出带头结点的单链表L中的所有节点值; //R_Print3(L); //printf("\n"); //4.删除带头结点的单链表L中最小值的节点高效方法(该最小值节点唯一) //Del_Min4(L); //5.带头结点的单链表逆置 //Reverse5(L); //6.使带头节点的单链表有序 //Sort_LinkList6(L); //7.删除无序带头节点的单链表L中值在(min,max)之间的节点 //Del_range(L,2 ,5); //8.给2个单链表,找出2个单链表的公共节点,就像一个躺着的Y //注意:怎么说了,这个单链表我又不能手动赋指定地址值,所以就实现不了。要想实现,就将找公共节点改为公共值的节点,节点值看成节点地址值。 //我也懒得写了,考试也考不到这里来,再过25分钟,距离考研只有43天了我线性代数都还没有复习完,我靠( ‵o′)凸。算了,洗漱早睡,明早看线性代数, ////Find_SameNode(L,L);不用打开,跑不起来的 //9.给定一个带头节点的单链表,递增输出各个节点的值,并释放节点空间 //思路?先找到单链表中元素最小的值,然后就删除该节点 //Del_Mins(L); //10.将单链表A分为2个单链表A,B。A中存原表中序号为奇数的节点,B中存原表中序号为偶数的节点。且保持相对位置不变 //LinkList B = Split2LinkList10(L);//(L相当于A) //printf("B链表输入如下:"); //PrintList(B); //printf("A链表输入如下:"); //11.将单链表A{a1,b1,a2,b2,a3,b3}分解为A{a1,a2,a3} B{b3,b2,b1}。此题和上一题要求差不多只不过B中元素逆置了.这里B中就用头插法来实现,A中还是用尾插法完成 //LinkList B = Split2LinkList11( L); //printf("B链表输入如下:"); //PrintList(B); //printf("A链表输入如下:"); //12.在有序的单链表中,删除值相同的节点(记得保留一个) //测试的时候,将头插法的那个数组改一下 //Del_Same(L); //13.将2个有序递增的单链表AB合并成一个有序递减的单链表A //LinkList B; //List_Insert_Tail(B); //MergeList(L,B); //14.将2个有序单链表中的公共值节点拼成一个新的链表C。不要删除原来节点的值 //LinkList C = Get_Common(L,L); //PrintList(C); //15.求2个有序链表的并集, //LinkList B; //List_Insert_Tail(B); //LinkList C = Union(L, B); //PrintList(C); //16.判断一个链表是否是另一个链表的子链表,也就是字符串判断他的子串 好像是Java中的Contains函数??唉!!为了复习考研我竟然有大半年没有写Java了。 //List_Insert_NoHead(L); // printf("%s\n", Pattern(L, L)?"是":"不是"); //17.判断一个循环双链表是否对称.不能测试,记住代码就行了 //// Symmetry(L); //18.将2个循环单链表合并成一个循环单链表,保持相对位置不变. 不能测试,记住代码就行了 ////Link(h1,h2) //19.依次输出循环单链表中的较小值。并删除最小值节点 ////Del_MinAll(L); //后面的不写了,广大考不了那么难,我的高数都还复习完,时间宝贵 //PrintList(L); //PrintList_NoHead(L); //==========================广大915真题篇============================== //2019第二题,要求:将一个带头节点的单链表L中值重复的多余节点删除,(就是去重) DelDistincts(L); PrintList(L); return 0; }