1、设n表示线性表中的元素个数, E表示存储数据元素所需的存储单元大小,D表示可以在数组中存储线性表的最大元素个数(D≥n),则采用顺序存储方式存储该线性表需要多少存储空间?
答案:
数组长度为D,每个数组元素占用E个存储单元,因此,该线性表采用顺序存储方式所需存储空间为E×D。
2、在什么情况下线性表使用顺序表存储比较好?
答案:
如果线性表很少进行插入和删除操作,但是常常进行查找操作,并且线性表中的最多元素个数已知,或线性表长度变化不大,使用顺序表存储比较好。
3、试以顺序表作存储结构,实现线性表就地置逆。
答案:
线性表的置逆即是将对称元素交换,设线性表的长度为n,则将线性表中第i个元素与第n-i-1个元素相交换。算法只需要一个辅助空间完成两个元素互换,因此空间复杂度为O(1)。注意到顺序表的下标从0开始,算法如下:
void Reverse(SeqList &L){
int i,temp,n = L.length;
for(i=0;i<n/2;i++){ /*此处i是数据元素的逻辑序号*/
temp = L.data[i]; /*交换元素*/
L.data[i] = L.data[n-i-1];
L.data[n-i-1] = temp;
}
}
4、设计一个时间复杂度为O(n)的算法,实现将数组A[n]中所有元素循环左移k个位置。
答案:
可以将这个问题看作是把序列ab转换成序列ba(a代表序列的前k个元素,b代表数组中余下的n-k个元素),先将a置逆得到,再将b置逆得到,最后将置逆得到=ba。注意数组的置逆操作需要限定起始位置和终止位置。
void Converse(char A[],int n,int k){
Reverse(A,0,k-1); /*将前k个元素置逆*/
Reverse(A,k,n-1); /*将后n-k个元素置逆*/
Reverse(A,0,n-1); /*将整个数组置逆*/
}
void Reverse(char A[],int from,int to){ /*将数组A[from]~A[to]置逆*/
int i,temp;
for(i=0;i<(to-from+1)/2;i++){
/*交换元素*/
temp = A[from+i];
A[from+i] = A[to-i];
A[to-i] = temp;
}
}
5、设n表示线性表中的元素个数, P表示指针所需的存储单元大小,E表示存储数据元素所需的存储单元大小,则使用单链表存储方式存储该线性表需要多少存储空间(不考虑头结点)?
答案:
每个结点所需空间为P+E,线性表中的每个元素对应单链表中的一个结点,单链表中共有n个结点,则共需要n(P+E)个存储单元。
6、用顺序表表示集合,设计算法实现集合的求交集运算。
答案:
本题即是求C=A∩B。扫描表A,对A中的元素A.data[i],若它与表B中某个元素相同,则表示是交集的元素,将其放到表C中。
void Interest(SeqList A,SeqList B,SeqList &C){
int i,j,k=0;
for(i=0;i<A.length;i++){
for(j=0;j<B.length;j++){
if(A.data[i]==B.data[i]) /*如果是相同元素,则退出循环*/
break;
}
if(j<B.length) /*是相同元素*/
C.data[k++]=A.data[i];
C.length=k; /*共有k个相同元素*/
}
}
7、若频繁地对一个线性表进行插入和删除操作,该线性表采用什么存储结构比较好?
答案:
采用链接存储结构,例如单链表。在链表上进行插入和删除操作不需移动元素,在指针指向合适结点后,只需修改相应指针。最坏情况下,需要从头指针开始查找相应结点,移动指针的平均次数是表长的一半;采用顺序存储结构,插入和删除一个元素需要移动元素的平均个数为表长的一半,通常移动指针比移动元素花费的时间少得多。
8、将值为x的结点插入到不带头结点的单链表L中值为k的结点之前,若找不到值为k的结点,则将x插入到链表的末尾。
答案:
注意题目要求不带头结点,首先需要判断单链表是否为空,如果链表为空,则将值为x的结点作为表的唯一结点插入链表中,这需要修改头指针L;如果链表不为空,则需要查找值为k的结点,由于需要插在结点k之前,需要标记结点k的前驱结点。还要考虑一个边界情况:如果链表第一个结点的值为k,则将值为x的结点作为第一个结点插入链表中,这需要修改头指针。算法如下:
void Insert(Node *L,int x,int k){
Node *s = NULL,*pre = NULL,*p = NULL;
s=(Node *)malloc(sizeof(Node));
s->data = x;
if(L == NULL || L->data == k){ /*表头插入的特殊情况*/
s->next = L;
L = s;
}
pre = L; /*pre是辅助工作指针*/
p = L->next; /*工作指针p初始化*/
while(p != NULL && p->data != x){
pre = p;
p = p->next; /*两个工作指针都要后移*/
}
s->next = pre->next;
pre->next = s; /*插在结点pre的后面*/
}
9、判断非空单链表是否递增有序。
答案:
若单链表的长度为1,则结论显然成立。若单链表的长度大于1,则需判断每个结点的值是否小于其后继结点的值,所以设两个工作指针p和q,p指向当前结点,q始终指向 p的后继(如果后继结点存在),在扫描过程中将p所指结点值和q所指结点值进行比较,然后p和q 同时后移。算法如下:
int Increase(Node *first){
Node *p = NULL,*q = NULL;
p = first->next; //指针p指向开始结点
while (p->next != NULL){ //当p的后继结点存在,进行比较
q-p->next;
if (p->data<q->data) p-q;
else return 0;
}
return l; //退出循环说明每个结点的值均小于其后继结点的值
}
10、已知非空线性链表由list指出,设计算法交换p所指结点与其后继结点在链表中的位置(设p所指结点不是链表的最后一个结点)。
答案:
在交换结点p和后继结点的位置时,不仅要考虑到结点内部指针的变化,还应该考虑结点p的前驱结点的指针,所以需要先查找结点p 的前驱结点。交换结点需要修改指针的顺序如图所示。
void Exchange(Node *list, Node *p){
Node *pre = NULL,*temp = NULL;
pre = list; //工作指针pre初始化
while (pre->next != p) //循环直到pre指向p的前驱
pre = pre->next;
temp = p->next; //暂存结点p的后继
pre->next = p->next;
p->next = temp->next;
temp->next = p;
}