前言
提示:全文为作者手打,仅供个人复习使用
#define maxSize 50
顺序表
typedef struct {
int data[maxSize];
int length;
}Sqlist;
单链表
typedef struct LNode{
int data;
struct LNode *next;
}LNode;
双链表
typedef struct DNode{
int data;
struct DNode *prev;
struct DNode *next;
}DNode;
一、基础题
-
线性表可用顺序表和链表存储,试问:
-
如果有n个表同时并存,并且在处理过程中各表的长度会动态发生变化,表的总数也可能自动改变,在此情况下,应选用哪种存储表示?为什么?
用链表。
如果采用顺序表,在多个表并存的情况下,一旦发现某个表有存满并溢出的情况,需要移动其他表来腾出空间为其扩容,导致不断把大片数据反复移动。时间开销太大且操作易出错。
若表的总数还要变化,则会带来在不影响其他表工作的情况下开辟新表空间或释放旧表空间的操作上的麻烦。
采用链表就没有这些问题
内存空间足够情况下,各表的空间分配或释放不受其他表的影响。 -
若表的总数基本稳定,且很少进行插入和删除但要求以最快的速度存取表中的元素,这时用哪种存储表示?为什么?
用顺序表、
顺序表插入,删除操作时间复杂度为O(n),存取操作时间复杂度为O(1)
充分发挥存取速度块、存储利用率高的特点
-
-
为什么在单循环链表中设置尾指针比设置头指针更好?
what:尾指针是指向终端结点的指针
why:使得查找链表的开始结点和终端结点的时间复杂度为O(1)
而设置头指针,查找终端结点的时间复杂度仍是O(n) -
设计一个算法,将顺序表中的所有元素逆置。
void swap(int *a, int *b){ int t = *a; *a = *b; *b = t; } void reverse(int *nums, int n){ for(int i=0, j=n-1; i<j; i++, j--){ myswap(&nums[i], &nums[j]); } }
void reverse(Sqlist &L){ int i, j; int temp; for(i=0, j=L.length-1; i<j; ++i, --j){ temp=L.data[i]; L.data[i]=L.data[j]; L.data[j]=temp; } }
-
设计一个算法,从一给定的顺序表L中删除下标i~j(i≤j,包括i、j)的所有元素,假定i、j都是合法的
void delete(Sqlist &L, int i, int j){ int k, delta; delta=j-i+1; for(k=j+1; k<L.length; k++){ L.data[k-delta]=L.data[k]; } L.length-=delta; }
L.length -= delta;
-
有一个顺序表L,其元素为整型数据,设计一个算法,将L中所有小于表头元素的整数放在前半部分,大于表头元素的整数放在后半部分
void move(Sqlist &L){ int temp=L.data[i], i=0, j=L.length-1; while(i<j){ while(i<j && L.data[j]>temp) j--; if(i<j){ L.data[i]=L.data[j]; i++; } while(i<j && L.data[i]<temp) i++; if(i<j){ L.data[j] = L.data[i]; j--; } } L.data[i] = temp; }
-
有一个递增非空单链表,设计一个算法删除值域重复的结点。例如,{1,1,2,3,3,3,4,4,7,7,7,9,9,9}经过删除后变成(1,2,3,4,7,9}
void delete(LNode *L){ LNode *p=L->next, *q; while(p->next != NULL){ if(p->data == p->next->data){ q = p->next; p->next = q->next; free(q); }else{ p = p->next; } } }
-
设计一个算法删除单链表L(有头结点)中的一个最小值结点
void delete(LNode *L){ LNode *pre=L, *p=pre->next, *minp=p, *minpre=pre; while(p != NULL){ if(p->data<minp->data){ min = p; minpre = pre; } pre = p; p = p->next; } minpre->next = minp->next; free(minp); }
-
有一个线性表,采用带头结点的单链表L来存储。设计一个算法将其逆置。要求不能建立新结点,只能通过表中已有结点的重新组合来完成。
void reverse(LNode *L){ LNode *p=L->next , *q; L->next=NULL; while(p != NULL){ q = p->next; p->next = L->next; L->next = p; p = q; } }
-
设计一个算法,将一个头结点为A的单链表(数据域为整数)分解成两个单链表A和B,得A链表只含有原来链表中data域为奇数的结点,而B链表只含有原链表中data域为偶数的结点,且保持原来的相对顺序。
void divide(LNode *A, LNode *&B){ LNode *p = A, *q; B = (LNode*)malloc(sizeof(LNode)); B->next = NULL; q = B; while(p->next!=NULL){ if(p->next->data % 2 == 0){ q->next = p->next; q = q->next; p->next = p->next->next; }else p = p->next; } q->next = NULL; }
二、思考题
-
有N个个位正整数存放在int型数组A[0,…,N-1]中,N为已定义的常量且N≤9,数组A[]的长度为N,另给一个int型变量i,要求只用上述变量(A[0]~A[N-1]与i,这N+1个整型变量)写一个算法,找出这N个整数中的最小者,并且要求不能破坏数组A[]中的数据。
void getMin(int[] nums, N){ int i = nums[0]; for(int j = 1; j<N; j++){ if(nums[j]<i) i = nums[j]; } }
-
写一个函数,逆序打印单链表中的数据,假设指针L指向了单链表的开始结点
dfs(L); void dfs(LNode *node){ if(node == NULL) return; dfs(node->next); printf(node->data); }
-
设有两个用有序链表表示的集合A和B,设计一个算法,判断它们是否相等
int equal(Sqlist &A, Sqlist &B){ if(A.length != B.length) return -1; for(int i = 0; i<A.length; i++){ if(A.data[i]!=B.data[i]) return -1; } return 1; }
-
设A=(a1,a2,…,am)和B=(b1,b2,…,bn)均为顺序表,A’和B’分别是除去最大公共前缀后的子表。例如,A=(b,e,i,j,i,n,g),B=(b,e,i,f,a,n,g),则两者的最大公共前缀为b、e、i,在两个顺序表中除去最大公共前缀后的子表分别为A’=(j,i,n,g),B’=(f,a,n,g)若A’=B’=空表,则A=B。若A’=空表且B’≠空表,或两者均不为空且A’的第一个元素值小于B’的第一个元素值,则A<B,否则A>B。所有表中元素均为 float型,试编写一个函数,根据上述方法比较A和B的大小。
void compare(){ }
-
键盘输入n个英文字母,输入格式为n、cl、c2、…、cn,其中n表示字母的个数。请编程用这些输入数据建立一个单链表,并要求将字母不重复地存入链表。
LNode *insert(int n){ char dic[26] = {0}; LNode *L = (LNode*)malloc(sizeof(LNode)); L->next = NULL; LNode *p = L, *q; for(int i = 0; i<n; i++){ char cur; scanf("%c", &cur); if(dic[cur-'0']==0){ dic[cur-'0']=1; q = (LNode*)malloc(sizeof(LNode)); q->data = cur; p->next = q; p = p->next; } } return L; }
三、真题精选
-
已知一个带有表头结点的单链表,结点结构为:
data next 假设该链表只给出了头指针head。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k(k为正整数)个位置上的结点。若查找成功,算法输出该结点的data值,并返回1;否则,只返回0.
要求:
(1)描述算法的基本设计思想。
(2)描述算法的详细实现步骤。
(3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C或C++语言实现),关键之处请给出简要注释。基本设计思想:
- 从头至尾遍历单链表,并用指针pre指向当前结点的前k个结点。
- 当遍历到链表的最后一个结点时,指针pre所指向的结点即为所查找的结点
详细实现步骤:
- 声明2个指针变量cur,pre,1个整型变量cnt
- 从链表头向后遍历,指针cur指向当前遍历的结点,结点pre指向cur所指结点的前k个结点,如果cur之前没有k个结点,那么pre指向表头结点。
- 用整型变量cnt表示当前遍历了多少个结点,当cnt>k时,指针pre随着每次遍历也向后移动一个结点。
- 当遍历完成时,pre要么指向表头指针,要么指向链表中倒数第k个位置上的结点
程序设计语言描述:
int find(LNode *L, int k){ LNode cur = L, pre = L; int cnt = 0; while(cur != NULL){ cur = cur->next; cnt++; /*if判断不能放前面*/ if(cnt > k){ pre = pre->next; } } if(cur == L ){ return 0; }else{ printf("%d", pre->data); return 1; } }
-
设将n(n>1)个整数存放到一维数组R中。设计一个在时间和空间两方面尽可能高效的算法。将R中保存的序列循环左移P(0<P<n)个位置,即将R中的数据由(X0,X1,…,Xn-1)变换为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1)。要求
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++语言描述算法关键之处给出注释。
(3)说明你所设计的算法的时间复杂度和空间复杂度。
基本设计思想:
- 将R中前P个元素逆置
- 将剩下的元素逆置
- 将R中所有的元素整体逆置
void swap(int[] R, int i, int j){ R[i] = R[i]^R[j]; R[j] = R[i]^R[j]; R[i] = R[i]^R[j]; } void reverse(int[] R, int i, int j){ for(; i<j; i++, j--){ swap(R, i, j); } } void solution(int[] R, int n, int p){ reverse(0, p-1); reverse(p, n-1); reverse(0, n-1); }
时间复杂度:O(n)
空间复杂度:O(1)3. 已知一个整数序列A=(a0,a1,…,an-1),其中0≤ai<n(0≤i<n)若存在ap1=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)给出算法的基本设计思想
(2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释
(3)说明你所设计算法的时间复杂度和空间复杂度
基本设计思想:
从前往后扫描数组元素,标记出一个可能成为主元素的元素num, 然后重新计数,确认num是否是主元素- 选取候选的主元素,依次扫描所给数组中的每个整数,用cnt记录num的出现次数,将第一个遇到的整数num保存到ans中,cnt=1
- 若遇到的下一个整数仍等于num,则cnt+=1,否则cnt-=1
- 当cnt减到0时,将遇到的下一个整数保存到ans中,cnt重新赋值1,开启新一轮计数,即从当前位置开始重复上述过程,直至扫描完全部数组元素
- 判断ans中元素是否时真正的主元素。再次扫描该数组,统计ans中元素出现的次数,若大于n/2,则为主元素;否则,序列中不存在主元素
算法实现:
int majority(int A[], int n){ int ans, cnt; ans = A[0]; for(int i=1; i<n; i++){ if(A[i] == ans){ cnt++; }else{ if(cnt > 0){ cnt--; }else{ ans = A[i]; cnt = 1; } } } if(cnt > 0){ for(i = cnt = 0; i<n; i++){ if(A[i] == ans) cnt++; } } return cnt>n/2? ans: -1; }
时间复杂度:O(n)
空间复杂度:O(1)
总结
- 😄
- 😢