1.一个顺序表(即一批地址连续的存储单元)第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是多少?
100+2*(5-1)=108
2.线性表的顺序存储结构和链式存储结构的优缺点是什么?
顺序表:
优点:存储密度大,无须为表示线性表中元素之间的逻辑关系而增加额外的存储空间。具有随机存取特性。
缺点:插入和删除操作需要移动大量元素。初始空间大小分配难以掌握。
链表:
优点:由于采用结点的动态分配方式,具有良好的适应性。插入和删除操作只需修改相关指针域,不需要移动元素。
缺点:存储密度小:为表示线性表中元素之间的逻辑关系而需要增加额外的存储空间(指针域)。不具有随机存取特性。
3. 顺序存储的特点是什么?链式存储结构的特点是什么?
顺序存储的特点:
(1)一片连续的存储单元存放元素,不需要为关系额外增加存储空间
(2)逻辑上相邻的元素,物理上也是相邻。
链式存储的特点:
(1)元素之间的关系用指针指示。需要额外增加存储空间来存放指针
(2)逻辑上相邻的元素,物理上不一定相邻。
4.如果存储某高校的某班级的同学录(人员名单基本固定),采用什么存储结构好,为什么?如果记录每天参加长跑的人员名单(每天的人员都不一样),采用什么存储结构好,为什么?
存储某高校的某班级的同学录最好用顺序存储,因为人员是固定的,不会出现插入删除人员的情况,顺序表的缺点避免了。每天参加长跑的人员名单最好用链表,因为人员不固定,频繁地插入删除人员。插入删除元素只要改一下指针即可。
5. 不带头结点的单链表head(指针域是next),判断空表的条件是什么?如果带头结点,判断空表的条件是什么?如果head带头结点且改成循环单链表,判断p所指向的结点是否为尾结点的条件是什么?
不带头结点的单链表判断空表的条件是head==NULL;
带头结点的单链表判断空表的条件是head->next==NULL;
带头结点且改成循环单链表尾结点的条件是p->next==head
6. 在双向循环链表中(向前的指针用left表示,向后的指针用right表示),在p结点之前插入s结点的操作语句是什么?在p结点之后插入s结点的操作语句是什么?删除p所指向结点的操作语句是什么?删除p所指向结点的后继结点的操作语句是什么?
在p结点之前插入s结点的操作语句:
s->left=p->left; s->right=p; p->left->right=s; p->left=s;
在p结点之后插入s结点的操作语句:
s->left=p; s->right=p->right; p->right->left=s; p-> right=s;
删除p所指向结点的操作语句:
p-> left->right=p->right;
if(p->right){p->right->left=p->left};
free(p);
删除p所指向结点的后继结点的操作语句
if(p->right)
{
q=p->right;
q->left->right=q->right;
if(q->right){q->right->left=q->left};
free(q);
}
7. 在一个单链表中(结点指针域是next),已知q所指结点是p所指结点的前驱结点,在q和p之间插入s结点的操作语句是什么?在p之后插入s结点的操作语句是什么?若删除p所指结点的后继结点,则执行什么操作语句?
q和p之间插入s结点的操作语句:
q->next=s; s->next=p;
p之后插入s结点的操作语句:
s->next=p->next; p->next=s;
删除p所指结点的后继结点的操作语句:
if(p->next)
{
q=p->next;
p->next=q->next;
free(q);
}
8. 在一个具有n个结点的单链表中查找其值等于x结点时,在查找成功的情况下,需要平均比较多少次?
如果第1个结点的值等于x时,比较1次
如果第2个结点的值等于x时,比较2次
……
如果第n个结点的值等于x时,比较n次
所以查找成功的平均比较次数是 (1+2+3+……+n)/n=(n-1)/2
9.在一个具有n个结点的有序单链表中插入一个新结点并仍然有序的时间复杂度是多少?
需要从链表开头查找,找到结点的插入位置,所以时间复杂度为O(n)
10. 给定n个元素的顺序表,建立一个有序单链表的时间复杂度是多少?(参考书上例2.8)
从空链表开始,把顺序表中的元素一个一个插入到有序链表中,而在有序链表中找到插入位置还需要遍历有序链表,所以要用两重循环,时间复杂度为O(n2)
11. 对于一个具有n个结点的单链表,在已知p所指结点后插入一个新结点的时间复杂度是多少?在给定值为x的结点后插入一个结点,必须先找到哪个结点,然后再修改指针?查找的时间复杂度是多少?
p所指结点后插入一个结点只要改一下指针即可,所以时间复杂度是O(1)
给定值为x的结点后插入一个结点,必须先找到值x的结点的前驱结点,然后再修改指针,查找的时间复杂度是O(n)
12 在一个长度为n的顺序表中第i个元素(1 ≤ i ≤ n+1)之前插入一个元素时,需向后移动多少个元素?删除第i个元素(1 ≤ i ≤ n)时,向前移动多少个元素?
插入移动n-i+1个元素,删除移动n-i个元素。
13. 线性表的顺序存储是通过什么来反映元素之间的逻辑关系,而链式存储结构是通过什么来反映元素之间的逻辑关系。
线性表的顺序存储是通过数组的下标,即存储位置是否相邻来反映元素之间的逻辑关系。链式存储结构通过指针来反映元素之间的逻辑关系。
14.什么叫随机存取特性?
存取任何一个元素的时间都是O(1)
15.只带尾针的循环单链表,其优点是什么?
优点是删除第一个元素、在第一个元素前面插入新元素、在尾元素的后面插入新元素的时间复杂度是O(1)。
比如:
- 合并两个单链表,把第一个链表的尾结点指针修改一下指向第二个链表的第一个元素即可,不用查找尾结点,时间复杂度就是O(1)。
- 约瑟夫问题用带尾指针的循环单链表比较好。如果第一次出列元素就是第一个结点,这时需要找到尾结点,再修改指针。如果带有尾指针,不用查找尾结点,就很方便。
2.2 算法设计题(不用抄题目,先写出算法思想再写出关键代码)
1. 设 A=(a1,a2,…,am) 和 B=(b1,b2,…,bn)均为线性表,试设计一个比较A,B大小的算法,存储结构采用顺序存储和链式存储两种方法。判断A和B大小的方法如下:(1表示A>B,-1表示A<B,0表示A=B,可以参照书上两路归并算法)
1)两个线性表对应元素比较,一旦发现不等,则返回值为1或-1;
2)两个线性表对应元素比较完毕并且完全相同时,若两个表都没有剩余元素,则返回值为0;否则,哪个表有剩余,哪个表就较大。
要求先写出算法思想和步骤,再写出关键代码(存储结构定义+一个函数,不需要写主程序)
1.1链式存储:
存储结构:链式存储
typedef struct lnode
{ ElemType data; /*数据域*/
struct lnode *next; /*指针域*/
} LNode,*LinkList;
算法思想:
1.依次扫描链表A和B中对应元素的值,直到有一个到末尾为止
如果A和B中对应元素的值不等时,哪一个值较大,哪个链表就大,算法立即结束
2. 如果A到了末尾,但B未到末尾,则A<B
如果A未到末尾,但B到了末尾,则A>B
如果A和B都到了末尾,则A=B
关键代码:假设采用带头结点的单链表
int ListComp(LinkList A,LinkList B)
{ LinkList p,q;
for(p=A->next ,q=B->next; p!=NULL&&q!=NULL; p=p->next, q=q->next)
//p和q都未到末尾
if(p->data != q->data) //一旦不等返回1和 -1
return p->data > q->data ? 1 : -1;
if(p!=NULL&&q==NULL) return 1; //p没到表尾,q到表尾
else if(p==NULL&&q!=NULL) return -1; //p到表尾,q没到表尾
else return 0; //p和q同时到表尾
}
显示 for循环执行的次数是算法的语句频度
假设A的长度为m,B的长度为n 则语句频度为min(m,n)
所以时间复杂度T(m,n)=O(min(m,n))
1.2顺序存储:
存储结构:顺序存储
typedef struct
{ ElemType elem[MAXSIZE]; /* 数组域 * /
int length; /* 线性表长域 * /
} SqList; /* 结构体类型名 */
算法思想:
依次扫描顺序表A和B中对应元素的值,直到有一个到末尾为止
如果A和B中对应元素的值不等时,哪一个值较大,哪个顺序表就大,算法立即结束
如果A的长度等于B的长度,则A=B
如果A的长度大于B的长度,则A>B
否则,则A<B
关键代码:
int ListComp(SqList A,SqList B)
{ int i;
for(i=0;i<A.length&&i<B.length;i++) //i未到末尾
if(A.elem[i]!=B.elem[i]) //一旦不等返回1和 -1
return A.elem[i]>B.elem[i] ? 1 : -1;
if(A.length==B.length) return 0; //表长相等
return A.length>B.length?1:-1; //表长不相等
}
显示 for循环执行的次数是算法的语句频度
假设A的长度为m,B的长度为n 则语句频度为min(m,n)
所以时间复杂度T(m,n)=O(min(m,n))
2.写一个算法,删除单链表中所有大于x且小于y的元素(若表中存在这样的元素)同时释放被删除结点空间。要求先写出算法思想和步骤,再写出关键代码。如果链表是有序的,则算法如果改进?(参考书上例2.16)改进后的时间复杂度是多少?然后再写出关键代码。
存储结构:链式存储
typedef struct lnode
{ ElemType data; /*数据域*/
struct lnode *next; /*指针域*/
} LNode,*LinkList;
算法思想:
遍历链表的每一个结点(记住其前驱结点p),判断其值(p的下一个结点)是否是在x和y之间,如果是的话,删除此结点(p的下一个结点)并释放空间(修改前驱结点p的指针)
关键代码:
void Delete_Between(LinkList &L,int x,int y)
{
LinkList p,q;
p=L; //p指向前驱结点,一开始指向头结点
while(p->next)
{ //当结点存在(p->next是要遍历的结点)
if (p->next->data>x&& p->next->data<y)
{ //p指向要删除结点的前驱,删除p的下一个结点q
q=p->next; p->next=q->next ;free(q);
}
else
p=p->next;
}
}
时间复杂度为O(n)
如果链表有序:
算法思想:
线性表中的元素的值递增有序,大于x且小于y的结点肯定连续在一起,先找到最后一个小于等于x的结点,连续删除小于y的结点直到大于等于y为止!
关键代码:
void Delete_Between(LinkList &L,int x,int y)
{
LinkList p,q,r;
p=L; //p指向前驱结点,一开始指向头结点
/*连续查找直到p是最后一个小于等于x的结点的*/
while(p->next&&p->next->data<=x)
{
p=p->next;
}
if(p->next) /*如果有比x大的元素(p的下一个结点存在)*/
{
q=p->next;
/*连续删除q,直到q是第一个大于等于y的结点*/
while(q&&q->data<y)
{
r=q;
q=q->next;
free(r);
}
p->next=q; //后面未删结点接到原链表
}
}
时间复杂度虽然为O(n),如果链表中大于等于y的结点存在,就不用再遍历了,节省了时间。