第二章 线性表
(一)顺序表
1.从顺序表中删除具有最小值的元素,并返回被删元素的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出运行。
bool Del_Min(SqList &L,ElemType &value){ if(L.length==0) return false; // 找最小的元素 value = L.data[0]; int pos = 0; for(int i=1;i<L.length;i++) if(L.data[i]<value){ min = L.data[i]; pos = i; } // 填补 L.data[pos] = L.data[L.length-1]; L.length--; return true; } |
2.设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为O(1)。
void Reverse(SqList &L){ ElemType temp; for(int i=0;i<L.length/2;i++){ temp = L.data[i]; L.data[i] = L.data[L.length-i-1]; L.data[L.length-i-1] = temp; } } // 位序 1 2 3 |4| 5 6 7 length=7 length/2=3 // 下标 0 1 2 |3| 4 5 6 --> 0-6 1-5 2-4 // 位序 1 2 3 4 || 5 6 7 8 length=8 length/2=4 // 下标 0 1 2 3 || 4 5 6 7 --> 0-7 1-6 2-5 3-4 |
3.对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素。【思路方法很重要】
void del_x_1(SqList &L,ElemType x){ int k=0; // k:非x的元素的个数 int i; for(i=0;i<L.length;i++){ if(L.data[i]!=x){ L.data[k] = L.data[i]; k++; } } L.length = k; } // 思想:逐个移动,k作为游标,遇到x不移动 // 1 2 x 3 x 4 // 1 2 3 4 void del_x_2(SqList &L,ElemType x){ int i=0,k=0; // k:x元素的个数 while(i<L.length){ if(L.data[i]==x) k++; else L.data[i-k] = L.data[i]; i++; } /* for(i=0;i<L.length;i++){ if(L.data[i]==x) k++; else L.data[i-k] = L.data[i]; } */ L.length = L.length-k; } // 思想: k作为偏移量 |
4.从有序顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。
bool Del_s_t(SqList &L,ElemType s,ElemType t){ if(s>=t || L.length==0) return false; int i = 0; while(i<L.length && L.data[i]<s) // 寻找值大于或等于s的第一个元素 i++; if(i>=L.length) return false; // 所有元素值都小于s int j =i; while(j<L.length && L.data[j]<=t) // 寻找大于t的第一个元素 j++; // 0 1 ··· i ··· j-1 | j ··· length-1 删除区域:[i,j-1] while(j<L.length) L.data[i++] = L.data[j++]; L.length = i; // 最后一个元素复制之后,i和j都多加了1跳出循环。length=尾部元素下标+1。正好抵消。 return true; } |
5.从顺序表中删除其值在给定值s与t之间(包含s和t,要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行。
bool Del_s_t_2(SqList &L,ElemType s,ElemType t){ if(s>=t || L.length==0) return false; int k=0; for(int i=0;i<L.length;i++){ if(L.data[i]<s && L.data[i]>t){ L.data[k] = L.data[i]; k++; } } L.length = k; } bool Del_s_t_2(SqList&L,ElemType s,ElemType t){ if(s>=t || L.length==0) return false; int i,k=0; for(i=0;i<L.length;i++){ if(L.data[i]>=s && L.data[i]<=t) k++; else L.data[i-k] = L.data[i]; } L.length -= k; return true; } |
6.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同。 【有难度】
bool Delete_Same(SqList &L){ // 有序顺序表 --> 值相同的元素在连续位置上 if(L.length==0) return false; int i,j; for(i=0,j=1;j<L.length;j++){ // 1 2 2 3 3 3 if(L.data[i]!=L.data[j]) L.data[++i] = L.data[j]; } L.length = i+1; return true; } // 类似于直接插入排序,初始时将第一个元素看作是不重复的序列。之后依次判断后面的元素与前面非重复有序表的最后一个元素是否相同。 // 若相同,j++ // 若不同,则插入到前面的非重复有序表的最后。 |
7.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
bool Merge(SeqList A,SeqList B,SeqList &C){ if(A.length+B.length > C.MaxSize) return false; int i=0, j=0, k=0; while(i<A.length && j<B.length){ if(A.data[i]<=B.data[i]) C.data[k++] = A.data[i++]; else C.data[k++] = B.data[i++]; } // 剩余部分复制 while(i<A.length) C.data[k++] = A.data[i++]; while(j<B.length) C.data[k++] = A.data[j++]; C.length = k; return true; } |
8.已知在一维数组A[m+n]中依次存放两个线性表(a1,a2,a3,…,am)和(b1,b2,b3,…,bn)。编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,b3,…,bn)放在(a1,a2,a3,…,am)的前面。
// 1) 全部逆置 bn bn-1 bn-2 ··· b3 b2 b1 am am-1 am-2 ··· a3 a2 a1 // 2) 分别逆置 b1 b2 b3 ··· bn a1 a2 a3 ··· am void Reverse(ElemType A[],int left,int right,int arraysize){ if(left>=right || right>=arraysize) return false; int mid = (left+right)/2; for(int i=0;i<=mid-left;i++){ ElemType temp = A[left+i]; A[left+i] = A[right-i]; A[right-i] = temp; } } // 0 1 |2| 3 4 5 0 1 2 |3| 4 5 6 // 0-5 1-4 2-3 0-6 1-5 2-4 void Exchange(ElemType A[],int m,int n,int arraysize){ // a [0,m-1] b [m,m+n-1] Reverse(A,0,m+n-1,arraysize); // --> b[0,n-1] a[n,m+n-1] Reverse(A,0,n-1,arraysize); Reverse(A,n,m+n-1,arraysize); } // eg. 1 2 3 | 4 5 6 7 --> 7 6 5 4 | 3 2 1 --> 4 5 6 7 | 1 2 3 |
9.线性表(a1,a2,a3,…,am)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,则将其与后继元素位置相交换,若找不到,则将其插入表中并使表中元素仍递增有序。
void SearchExchangeInsert(ElemType A[],ElemType x,int n){ // 查找位置 int low=0,high=n-1,mid; while(low<=high){ mid = (low+high)/2; if(A[mid]==x) break; else if(A[mid]<x) low = mid+1; else high = mid-1; } // 交换 if(A[mid]==x && mid!=n-1) { // 如果找到了x,并且这个元素不在数组的最后一个位置 ElemType t = A[mid]; A[mid] = A[mid+1]; A[mid+1] = t; }
//插入 else if(low>high){ // 没有x,插入该值 // 0 1 2 ··· high low ··· n-1 for(int i=n-1;i>=low;i--){ A[i+1] = A[i]; A[i+1] = x; } } } |
10.【2010】设将n(n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0,X1,…,Xn-1)变换为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1)。
// 算法思想:可将数组看为 a b两部分,对a部分进行逆置,再对b部分进行逆置,然后对整体进行逆置。 // 0 1 ··· p-1 || p ··· n-1 // eg. 1 2 3 | 4 5 6 7 --> 3 2 1 | 7 6 5 4 --> 4 5 6 7 | 1 2 3 void Reverse(int A[],int start,int end){ int i,temp; for(i=0;i<(end-start+1)/2;i++){ temp = A[start+i]; A[start+i] = A[end-i]; A[end-i] = temp; } } // 相当于从首尾开始,两两交换。 // 1 2 3 | 4 5 6 i<(5-0+1)/2 1 2 3 | 4 | 5 6 7 i<(6-0+1)/2 // 0 1 2 | 3 4 5 i<3 0 1 2 | 3 | 4 5 6 i<3 void Converse(int A[],int n,int p){ Reverse(A,0,p-1); Reverse(A,p,n-1); Reverse(A,0,n-1); } // 方法2:借助辅助数组。略 |
【没看明白】
11.【2011】一个长度为L(L≥1)的升序序列S,处在第L/2(向上取整)个位置的数称为S的中位数。例如,若序列S1=(11,13,15,17,19),则S1的中位数是15,两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若S2=(2,4,6,8,20),则S1和S2的中位数是11。现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数。
// 思想:对两个等长升序序列A、B,分别求中位数a、b。 // 1) 若a==b,结束。 // 2) 若a<b,舍弃A较小的一半,舍弃B较大的一半,舍弃长度需要相等。????? // 3) 若a>b,舍弃B较小的一半,舍弃A较大的一半,舍弃长度需要相等。 // 递归执行上一过程,直至两个序列中只剩下一个元素,较小者即为所求中位数。 int M_Search(int A[],int B[],int n){ int s1=0,e1=n-1,m1; // A s1 ··· e1 int s2=0,e2=n-1,m2; // B s2 ··· e2 while(s1!=e1 || s2!=e2){ m1 = (s1+e1)/2; m2 = (s2+e2)/2;
// 1) 两序列的中位数相等 if(A[m1] == B[m2]) return A[m1];
// 2) A序列的中位数 < B序列的中位数 else if(A[m1]<B[m2]){ if((s1+e1)%2==0){ // 元素个数为奇数 1 2 || (·3· 4 5) mid=3 s1 = m1; // 舍弃A序列较小的部分,保留中间点 (5 6 ·7·) || 8 9 mid=7 e2 = m2; // 舍弃B序列较大的部分,保留中间点 }else{ // 元素个数为偶数 s1 = m1+1; // 舍弃A序列较小的部分,不保留中间点 1 2 3 ·4· || (5 6) mid=4 e2 = m2; // 舍弃B序列较大的部分,保留中间点 (5 6 7 ·8·) || 9 10 mid=8 } } ????????????????????????????????? // 3) A序列的中位数 > B序列的中位数 else{ if((s2+e2)%2==0){ // 元素个数为奇数 e1 = m1; s2 = m2; }else{ // 元素个数为偶数 e1 = m1; s2 = m2+1; } } } return A[s1]<B[s2] ? A[s1]:B[s2]; } |
12【2013】已知一个整数序列A=(a0,a1,…,an-1),其中0≤ai<n(0≤i<n)。若存在p1=ap2=···=apm=x 且m>n/2(0≤pk<n,1≤k≤m),则称x为A的主元素。例如A=(0,5,5,3,5,7,5,5),则5为主元素;又如A=(0,5,5,3,5,1,5,7),则A中没有主元素。假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素;否则输出-1.
// 思路: 1) 依次遍历数组,将第一个遇到的整数num进行保存,记录其出现次数为1。当遇到的下一个整数仍为num,计数+1,否则计数-1。当计数减到0时,从当前位置继续重复此过程。
// 2) 再次扫描此数组,判断保存的数是否是真正的主元素。
int Majority(int A[],int n){ int main_element = A[0]; int count = 1; int i; for(i=0;i<n-1;i++){ if(A[i] == main_element) count++; else if(count>0) count--; else{ main_element = A[i]; count = 1; } } if(count>0){ // 统计主元素出现次数 for(i=0,count=0;i<n;i++){ if(A[i]==main_element) count++; } } if(count>n/2) return main_element; else return -1; // 不存在主元素 } // 也可以先排序再进行统计 |
13.【2018】给定一个含n(n>1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。例如,数组(-5,3,2,3)中未出现的最小正整数是1;数组(1,2,3)中未出现的最小正整数是4.
// 思路:时间高效 --> 空间换时间
// 使用同样长度的数组标记各整数是否出现
int findMissMin(int A[],int n){ int *B = (int *)malloc(sizeof(int)*n); int i; for(i=0;i<n;i++) B[i] = 0; for(i=0;i<n;i++){ if(A[i]>0 && A[i]<=n) B[A[i]-1] = 1; } for(i=0;i<n;i++){ if(B[i]==0) break; } return i+1; } |
// 数组B 下标[0,n-1] --> 对应 1~n
// 极端情况:如果n个数都大于n,那么数组中全部为0
// 极端情况:如果n个数都小于n,那么数组中全部为1,i会走到最后
14【2020】定义三元组(a, b, c) (a、b、c均为正数)的距离D=|a-b|+|b-c|+|c-a|。给定3个非空整数集合S1、S2和S3,按升序分别存储在3个数组中。请设计一个尽可能高效的算法,计算并输出所有可能的三元组(a,b,c)(a∈S1, b∈S2, c∈S3)中的最小距离。例如S1 = {-1, 0, 9}, S2={-25, -10, 10, 11}, S3 = {2, 9, 17, 30, 41},则最小距离为2,相应的三元组为(9, 10,9)。
// 思路: D=|a-b|+|b-c|+|c-a| 1) a=b=c时,距离最小
// 2) 其他一般情况 假设 a<=b<=c 画数轴 L1=|a-b| L2=|c-b| L3=|c-a| L=2L3
// 问题转化 ----> 每次固定一个c找一个a,使得L3=|c-a|最小
#define INT_MAX 0x7fffffff int abs(int num){ if(num<0) return -num; return num; } bool firstIsMin(int a,int b,int c){ if(a<b && a<c) return true; return false; } int findMinofTrip(int A[],int n,int B[],int m,int C[],int p){ int i=0,j=0,k=0; int D,D_min=INT_MAX; while(i<n && j<m && k<p && D_min>0){ D = abs(A[i]-B[j]) + abs(B[j]-C[k]) + abs(C[k]-A[i]); if(D<D_min) D_min = D; if(firstIsMin(A[i],B[j],C[k])) i++; else if(firstIsMin(B[j],A[i],C[k])) j++; else k++; } return D_min; } |
(二)链表
1.设计一个递归算法,删除不带头结点的单链表L中所有值为X的结点。
void Delete_X_1(LinkList &L,ElemType x){ // 递归删除元素为X的结点 if(L==NULL) // 递归出口 return; if(L->data == x){ LNode *p = L; L = L->next; free(p); Delete_X(L,x); }else{ // 当前结点的值不是x Delete_X(L->next,x); } } |
2.在带头结点的单链表L中,删除所有值为X的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
void Delete_X_2(LinkList &L, ElemType x){ // 逐个遍历寻找值为X的结点并删除 LNode *p = L->next; LNode *pre = L; LNode *temp; while(p!=NULL){ if(p->data == x){ temp = p; p = p->next; pre->next = p; free(p); }else{ pre = pre->next; p = p->next; } } } void Delete_X_3(LinkList &L,ElemType x){ // 逐个遍历结点,符合条件的删除,不符合的进行尾插 LNode *r = L->next; // 游标 LNode *tail = L; // 指示链表尾部 LNode *temp; while(r!=NULL){ if(r->data!=x){ // 当前结点的值不是x tail->next = r; // 将当前结点尾插 tail = r; r = r->next; // 游标后移 }else{ temp = r; r = r->next; free(temp); } tail->next = NULL; } } |
3. 设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
// 思路:1)链表逆置后输出 2)借助栈来实现
// 3)使用递归的方法来实现
void Print(LinkList L){ if(L->next!=NULL) Print(L->next); if(L!=NULL) printf(L->data); } void Ignore_Head(LinkList L){ if(L->next!=NULL) Print(L->next); } |
4. 试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
void Delete_Min(LinkList &L){ LNode *r = L->next; LNode *pre = L; LNode *min = r, *minpre = L; while(r!=NULL){ if(r->data < min->data){ min = r; minpre = pre; } pre = r; r = r->next; } minpre->next = min->next; free(min); } |
5.试编写算法将带头结点的单链表就地逆置,所谓就地就是指辅助空间复杂度为O(1)。
void Reverse(LinkList &L){ // 头插法逆置链表 LNode *r = L->next; L->next = NULL; LNode *temp; while(r!=NULL){ temp = r; r = r->next; temp->next = L->next; L->next = temp; } } |
6.有一个带头结点的单链表L,设计一个算法使其元素递增有序。
void Sort(LinkList &L){ // 直接插入排序 LNode *r = L->next->next; L->next->next = NULL; LNode *temp,*pre; while(r!=NULL){ temp = r; r = r->next; pre = L; while(pre->next->data < temp->data && pre->next!=NULL) pre = pre->next; temp->next = pre->next; pre->next = temp; } } |
7.设在一个带头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)。
void Delete_Range(LinkList &L,int min,int max){ LNode *r = L->next; LNode *pre = L; while(r!= NULL){ if(r->data > min && r->data <max){ pre->next = r->next; free(r); r = pre->next; }else{ pre = r; r = r->next; } } } |
8.给定两个单链表,编写算法找出两个链表的公共结点。
// 思路:1)在第一个链表上顺序遍历,每遍历一个结点,在另一个链表上顺序遍历一次。
// 2)如果有公共结点,说明两结点的 |data|next| 是一模一样的,则后面的结点 ~~ 最后一个结点必然是重合的。
// 然而两链表长度不同,顺序遍历无法保证同时到达尾结点。 --> 计算两链表长度之差,同步遍历
LinkList SearchSame(LinkList L1,LinkList L2){ LinkList *longList, *shortList; int len1 = Length(L1), len2 = Length(L2); int distance; if(len1 > len2){ longList = L1; shortList = L2; distance = len1-len2; }else{ longList = L2; shortList = L1; distance = len2-len1; } while(distance--) longList = longList->next; // 同步遍历 while(longlist!=NULL){ if(longList==shortList) return longList; else{ longList = longList->next; shortList = shortList->next; } } return NULL; } |
9.给定一个带表头结点的单链表,设head为头指针,结点结构为(data,next),data为整型,next为指针。试写出算法:按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间。(不允许使用数组作为辅助空间)
void Delete_Ascending_Order(LinkList &head){ while(head->next!=NULL){ LNode *pre = head; // 最小值结点的前驱指针 LNode *r = head->next; // 游标 while(r->next != NULL){ if(r->next->data < pre->next->data) pre = r; r = r->next; } prinf(pre->next->data); LNode *temp = pre->next; pre->next = temp->next; free(temp); } free(head); } |
10.将一个带头结点的单链表A分解成为两个带头结点的单链表A和B,使得A表中含有原表中序数为奇数的元素,而B表中含有原表中序数为偶数的元素,且保持相对顺序不变。
LinkList Seperate(LinkList &A){ int num = 0; LinkList B = (LinkList)malloc(sizeof(LNode)); B->next = NULL; LNode *r = A->next; // 游标 A->next = NULL; LNode tail_A = A; // A链表的表尾 LNode tail_B = B; // B链表的表尾 while(r!=NULL){ num++; if(num%2==0){ // 序数为偶数 tail_B->next = r; tail_B = r; }else{ tail_A->next = r; tail_A = r; } r = r->next; } tail_A->next = NULL; tail_B->next = NULL; return B; } |
11.设C={a1,b1,a2,b2,···,am,bn}为线性表,采用带头结点的单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1,a2,···,am},B={bn,···,b2,b1}。
// 思路:和上题一样,区别在于对表B的建立采用头插法,需要注意的是头插法要防止丢失后继指针,造成断链。
LinkList Seperate_2(LinkList &A){ LinkList B = (LinkList)malloc(sizeof(LNode)); B->next = NULL; LNode *r = A->next; LNode* tail_A = A; while(r!=NULL){ tail_A->next = r; tail_A = r; r = r->next; if(r!=NULL){ LNode *temp = r; r = r->next; temp->next = B->next; B->next = temp; } } tail_A->next = NULL; return B; } |
12.在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,例如(7,10,10,21,30,42,42,51,70)将变为(7,10,21,30,42,51,70)。
// 思路:有序表--> 逐个扫描各节点,若当前结点的值域 == 后方结点的值域 --》 删除后者
void Del_Same(LinkList &L){ LNode *r = L->next; LNode *temp; if(r==NULL) return; while(r->next!=NULL){ temp = r->next; if(r->data == temp->data){ r->next = temp->next; free(temp); }else{ r = r->next; } } } |
13.假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将两个链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。
// 思路:采用头插法
void MergeList(LinkList &La,LinkList &Lb){ LNode *ra = La->next; LNode *rb = Lb->next; La->next = NULL; // 将La作为最终的结果链表 LNode *temp; while(ra&&rb){ // 两个待归并的链表都非空 if(ra->data <= rb->data){ temp = ra; ra = ra->next; temp->next = La->next; La->next = temp; }else{ temp = rb; rb = rb->next; temp->next = La->next; La->next = temp; } } if(ra) // 如果ra不为空(-->rb为空) rb = ra; // 将ra赋给rb //ra为空 --> rb可能不为空 //简化后续代码的编写,无需分别针对ra和rb写 while(rb){ temp = rb; rb = rb->next; temp->next = La->next; La->next = temp; } free(Lb); } |
14.设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C。要求不破坏A、B的结点。
LinkList Generate_Same(LinkList &A,LinkList &B){ LNode *ra = A->next; LNode *rb = B->next; LNode *temp; LinkList C = (LinkList)malloc(sizeof(LNode)); C->next = NULL; LNode tail = C; while(ra!=NULL && rb!=NULL){ if(ra->data == rb->data){ temp = (LNode*)malloc(sizeof(LNode)); temp->data = ra->data; tail->next = temp; // 尾插到新链表 tail = temp; ra = ra->next; //后移 rb = rb->next; }else if(ra->data < rb->data){ ra = ra->next; }else{ rb = rb->next; } } tail->next = NULL; } |
15.已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A和B的交集,并存放于A链表中。
// 与上一题差别:结果存放在A链表中
LinkList Union(LinkList &A,LinkList &B){ LNode *ra = A->next; LNode *rb = B->next; LNode *temp; LNode *tail = A; while(ra&&rb){ if(ra->data == rb->data){ tail->next = ra; // 尾插到结果链表中 tail = ta; ra = ra->next; temp = rb; // 删除B结点中的该元素 rb = rb->next; free(temp); }else if(ra->data < rb->data){ temp = ra; ra = ra->next; free(temp); }else{ temp = rb; rb = rb->next; free(temp); } } while(ra){ temp = ra; ra = ra->next; free(temp); } while(rb){ temp = rb; rb = rb->next; free(temp); } tail->next = NULL; free(B); return A; } |
16.两个整数序列A=a1,a2,···,am和B=b1,b2,···,bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
int Pattern(LinkList A,LinkList B){ LNode *ra = A; // 每趟遍历的的开始结点 LNode *rra = ra; // 二级游标 LNode *rb = B; while(ra&&rb){ if(rra->data == rb->data){ rra = rra->next; rb = rb->next; } else{ ra = ra->next; rra = ra; rb = B; } } if(rb==NULL) return 1; else return 0; } |
17.设计一个算法用于判断带头结点的循环双链表是否对称。
// 循环跳出条件:结点个数为奇数(p==q) 结点个数为偶数(p->next==q 或 q->prior==p)
bool Symmetry(DLinkList L){ DNode *p = L->next; DNode *q = L->prior; while(p!=q && p->next!=q){ if(p->data = q->data){ p = p->next; q = q->prior; }else{ return false; } } return true; } |
18.有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。
LinkList Link(LinkList &h1,LinkList &h2){ LNode *p = h1; LNode *q = h2; while(p->next!=h1) p = p->next; while(q->next!=h2) q = q->next; p->next = h2; q->next = h1; return h1; } |
19.设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该节点从中删除,直到单链表空为止,再删除表头结点。
void Delete_All(LinkList &L){ LNode *r,*pre,*min,*minpre; while(L->next != L){ r = L->next; pre = L; min =r; minpre = pre; while(r!=L){ // 寻找最小结点 if(r->data < min->data){ min = r; minpre = pre; }else{ pre = r; r = r->next; } } printf("%d",min->data); minpre->next = min->next; free(min); } free(L); } |
20.设头指针为L的带有头结点的非循环双链表,其每个结点中除了有pre(前驱指针)、data(数据)和next(后继指针)外,还有一个访问频度freq。链表在启用前,其值均初始化为0。每当在链表中进行一次Locate(L,x)运算时,令元素值为x的结点中freq域的值加1,并使此链表中的结点保持按照访问频度非增(递减)的顺序排列,同时最近访问的结点排在访问频度相同的结点前面,以使频繁访问的结点总是靠近表头。试编写符合上述要求的Locate(L,x)运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。
typedef struct DNode{ ElemType data; struct DNode *next; struct DNode *prior; int freq; }DNode,*DLinkList; DNode* Locate(DLinkList &L,ElemType x){ DNode *r = L->next; DNode *pre; while(r!=NULL && r->data!=x) r = r->next; if(r==NULL) // 不存在值为x的结点 exit(0); else{ // 存在值为x的结点 r->freq++; if(r->prior==L || r->freq < r->prior->freq) //当前结点是第一个结点 或 当前结点的访问频率<前驱结点的访问频率 return r; // 将该结点摘下 if(r->next != NULL) r->next->prior = r->prior; r->prior->next = r->next; // 寻找插入位置 pre = r->prior; while(pre!=L && pre->freq<=r->freq) pre = pre->prior; // 插入在同频率的第一个 pre r->next = pre->next; if(pre->next!=NULL) pre->next->prior = r; r->prior = pre; pre->next = r; } return r; } |
21.单链表有环,是指单链表的最后一个结点的指针指向了链表中的某个结点(通常单链表的最后一个结点的指针域是空的)。试编写算法判断单链表是否有环。
// 思路:设置两个指针:快指针、慢指针。初始时指向表头,slow每次走一步,fast每次走两步。fast先入环,最终两个指针会在环中相遇。
// 环的入口点:一个指针指向head 另一个指针指向相遇点,同步移动,相遇点即为环的入口点。【具体原理不是很明白,见示意图及其分析】
LNode* FindLoopStart(LNode* head){ LNode *fast = head; LNode *slow = head; while(fast!=NULL && fast->next!=NULL){ slow = slow->next; fast = fast->next->next; if(fast == slow) // 快慢指针相遇 break; } if(fast==NULL || fast->next==NULL) // 没有环 return NULL; // 寻找环的入口点 LNode *p1 = head; LNode *p2 = slow; while(p1!=p2){ p1 = p1->next; p2 = p2->next; } return p1; } |
22.[2009]已知一个带头结点的单链表,结点结构为 data,link。假设该链表只给出了头指针list,在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点。若查找成功,算法输出该结点的data域的值,并返回1,否则只返回0。
// 思路:使用两个指针,当一个指针移动到第k个元素时,另一个指针开始移动。
typedef struct LNode{ ElemType data; struct LNode* link; }LNode,*LinkList; int Search_k(LinkList list,int k){ LNode *des = list->link; LNode *fast = list->link; int num = 0; while(fast!=NULL){ if(num<k){ num++; fast = fast->link; } else{ des = des->link; fast = fast->link; } } if(num<k) return 0; else{ printf("%d",des->data); return 1; } } |
23.[2012]假定使用带头结点的单链表保存单词,当两个单词有相同后缀时,可共享相同的后缀存储空间,例如,loading和being的存储映像如下图所示。
// 设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为[data,next],设计一个在时间上高效的算法,找出共同后缀的起始位置。
// 思路:双指针法。1)求出各链表的长度 2)将两个链表的表尾对齐。一个指针先走,走到 m-n+1 或 n-m+1 的位置。然后同步移动。
int length(LNode *L){ int len = 0; while(L->next != NULL){ len++; L = L->next; } return len; } LNode* FindSame(LNode* str1,LNode* str2){ int len1 = length(str1); int len2 = length(str2); LNode* p = str1; LNode* q = str2; // 对齐链表 while(len1>len2){ p = p->next; len1--; } while(len1<len2){ q = q->next; len2--; } // 同步后移 while(p->next!=NULL && p->next!=q->next){ p = p->next; q = q->next; } return p->next; } |
24.[2015]用单链表保存m个整数,结点结构为[data,link],且|data|<=n(n为正整数)。设计一个时间复杂度尽可能高效的算法,对于链表中的data的绝对值相等的结点,仅保留第一次出现的结点
// 而删除其他绝对值相等的结点。例如,若给定的单链表head如下,21 -15 -15 -7 15 --》 21 -15 7
void Delete_AbsSame(LinkList &L,int n){ int *arr = (int *)malloc(sizeof(int)*(n+1)); // 取值范围 [0,n] 共n+1个 for(int i=0;i<n+1;i++) arr[i] = 0; //*(arr+i) = 0; LNode *r = L; LNode *temp; int value; while(r->link != NULL){ value = r->link->data>0 ? r->link->data : -(r->link->data); if(arr[value] == 0){ // 首次出现 arr[value] == 1; // *(arr+value) == 1 r = r->link; }else{ // 非首次出现 --》删除 temp = r->link; r->link = temp->link; free(temp); } } free(arr); } |
25.设线性表L=(a1,a2,a3,···,an-2,an-1,an)采用带头结点的单链表保存。请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L=(a1,an,a2,an-1,a3,an-2···)
// 思路:将链表的后半段原地逆置(题目有限制,不能使用栈)。
// 1)找中间结点。使用快慢指针,快指针到表尾的时候,慢指针恰好到链表的中间结点。2)将链表的后半部分原地逆置 3)按要求进行插入
void change_list(LinkList &L){ // 1 2 3| 4 5 6 1 2 3 4| 5 6 7 // 找中间结点 LNode *fast = L->next; LNode *slow = L->next; while(fast->next!=NULL && fast->next->next!=NULL){ fast = fast->next->next; slow = slow->next; } // 将后半段逆置(头插到slow结点后) LNode *r = slow->next; slow->next = NULL; LNode *temp; while(r!=NULL){ temp = r; r = r->next; temp->next = slow->next; slow->next = temp; } Show(L); // 按要求进行插入 LNode *p = L->next; r = slow->next; slow->next = NULL; // 不要忘记断开,否则会出现循环链表 while(r!=NULL){ temp = r; r = r->next; temp->next = p->next; p->next = temp; p = temp->next; } } |