成语大全网 - 成语解释 - 用算法实现:单链表和顺序表删除。删除顺序表中值相同的多余结点

用算法实现:单链表和顺序表删除。删除顺序表中值相同的多余结点

第8章排序(算法设计)习题练习答案

作者: 来源: 2006年9月4日

13. 将哨兵放在R[n]中,被排序的记录放在R[0..n-1]中,重写直接插入排序算法。

解:

重写的算法如下:

void InsertSort(SeqList R)

{//对顺序表中记录R[0..n-1]按递增序进行插入排序

int i,j;

for(i=n-2;i>=0;i--) //在有序区中依次插入R[n-2]..R[0]

if(R[i].key>R[i+1].key) //若不是这样则R[i]原位不动

{

R[n]=R[i];j=i+1; //R[n]是哨兵

do{ //从左向右在有序区中查找插入位置

R[j-1]=R[j]; //将关键字小于R[i].key的记录向右移

j++;

}while(R[j].key<R[n].key]);

R[j-1]=R[n]; //将R[i]插入到正确位置上

}//endif

}//InsertSort.

14.以单链表作为存储结构实现直接插入排序算法。

解:

#define int KeyType //定义KeyType 为int型

typedef struct node{

KeyType key; //关键字域

OtherInfoType info; //其它信息域,

struct node * next; //链表中指针域

}RecNode; //记录结点类型

typedef RecNode * LinkList ; //单链表用LinkList表示

void InsertSort(LinkList head)

{//链式存储结构的直接插入排序算法,head是带头结点的单链表

RecNode *p,*q,*s;

if ((head->next)&&(head->next->next))//当表中含有结点数大于1

{

p=head->next->next;//p指向第二个节点

head->next=NULL;

q=head;//指向插入位置的前驱节点

while(p)&&(q->next)&&(p->key<q->next->key)

q=q->next;

if (p)

{s=p;p=p->next;// 将要插入结点摘下

s->next=q->next;//插入合适位置:q结点后

q->next=s;

}

}

}

15.设计一算法,使得在尽可能少的时间内重排数组,将所有取负值的关键字放在所有取非负值的关键字之前。请分析算法的时间复杂度。

解:

因为只需将负数关键字排在前面而无需进行精确排列顺序,因此本算法采用两端扫描的方法,就象快速排序采用的方法一样,左边扫描到正数时停止,开始扫描右边,遇到负数时与左边的当前记录交换,如此交替进行,一趟下来就可以完成排序。

void ReSort(SeqList R)

{//重排数组,使负值关键字在前

int i=1,j=n; //数组存放在R[1..n]中

while (i<j) //i<j表示尚未扫描完毕

{ while(i<j&&R[i].key<0) //遇到负数则继续扫描

i++;

R[0]=R[i]; //R[0]为辅助空间

while(i<j&&R[j].key>=0)// 遇到正数则继续向左扫描

j--;

R[i++]=R[j];R[j--]=R[0];//交换当前两个元素并移动指针

}//endwhile

}//ReSort

本算法在任何情况下的比较次数均为n(每个元素和0)相比,交换次数少于n/2,总的来说,时间复杂度为O(n).

*16.写一个双向冒泡排序的算法,即在排序过程中交替改变扫描方向。

解:

算法如下:

void BubbleSort(SeqList R)

{//R[1..n]是待排序文件,双向扫描冒泡排序

int i,j,k;

Boolean exchange; //交换标记

i=n;j=1;

exchange=TRUE;

while (i>j)&&(exchange)

{k=i-1;exchange=FALSE;

while (k>=j)//由下往上扫描

{if (r[k]>r[k+1])

{r[0]=r[k];r[k]=r[k+1];r[k+1]=r[k];exchange=TRUE;//交换

}//endif

k--;

}//endwhile

if (exchange)

{exchange=FALSE;

j++;k=j+1;

while(k<=i)//由上往下扫描

{if (r[k]<r[k-1])

{r[0]=r[k];r[k]=r[k-1];r[k-1]=r[k];exchange=TRUE;//交换

}//endif

k++;

}endwhile

i--;

}//endif

}endwhile

}//endsort

17.下面是一个自上往下扫描的冒泡排序的伪代码算法,它采用lastExchange 来记录每趟扫描中进行交换的最后一个元素的位置,并以它作为下一趟排序循环终止的控制值。请仿照它写一个自下往上扫描的冒泡排序算法。

void BubbleSort(int A[],int n)

//不妨设A[0..n-1]是整型向量

int lastExchange,j,i=n-1;

while (i>0)

lastExchange=0;

for(j=0;j<i;j++)//从上往下扫描A[0..i]

if(A[j+1]<A[j]){

交换A[j]和A[j+1];

lastExchange=j;

}

i=lastExchange;//将i置为最后交换的位置

}//endwhile

}//BubbleSort

解:算法如下:

void BubbleSort(int A[],int n)

//不妨设A[0..n-1]是整型向量

int lastExchange,j,i=0;

while (i<n) //这一条很重要,如不改为这样,算法将无限循环下去

lastExchange=n;

for(j=n-1;j>i;j--)//从下往上扫描A[0..i]

if(A[j-1]<A[j]){

交换A[j]和A[j-1];

lastExchange=j;

}

i=lastExchange;//将i置为最后交换的位置

}//endwhile

}//BubbleSort

18.改写快速排序算法,要求采用三者取中的方式选择划分的基准记录;若当前被排序的区间长度小于等于3时,无须划分而是直接采用直接插入方式对其排序。

解:

改写后的算法如下:

void QuickSort(SeqList R,int low ,int high)

{//对R[low..high]快速排序

int pivotpos;

if(high-low<=2)//若当前区内元素少于3个

{//则进行直接插入排序

InsertSort(R,low,high);

}

else

{

pivotpos=midPartion(R,low,high);

QuickSort(R,low,pivotpos-1);

QuickSort(R,pivotpos+1,high);

}

}//QuickSort

int midPartion(SeqList R,int i, int j)

{//三者取中规则定基准

if(R[(i+j)/2].key>R[i].key)

{ 交换R[(i+j)/2]和R[i];}

if(R[i].key>R[j].key)

{ 交换R[i]和R[j];}

if(R[i].key)<R[(i+j)/2].key)

{ 交换R[i]和R[(i+j)/2];}

//以上三个if语句就使区间的第一个记录的key值为三个key的中间值

return Partion(R,i,j);//这样我们就可以仍使用原来的划分算法了

}

19.对给定的j(1≤j≤n ),要求在无序的记录区R[1..n]中找到按关键字自小到大排在第j个位置上的记录(即在无序集合中找到第j个最小元),试利用快速排序的划分思想编写算法实现上述的查找操作。

答:

int QuickSort(SeqList R,int j,int low,int high)

{ //对R[low..high]快速排序

int pivotpos; //划分后的基准记录的位置

if(low<high){//仅当区间长度大于1时才须排序

pivotpos=Partition(R,low,high); //对R[low..high]做划分

if (pivotpos==j) return r[j];

else if (pivotpos>j) return(R,j,low,pivotpos-1);

else return quicksort(R,j,pivotpos+1,high);

}

} //QuickSort

20.以单链表为存储结构,写一个直接选择排序算法。

答:

#define int KeyType //定义KeyType 为int型

typedef struct node{

KeyType key; //关键字域

OtherInfoType info; //其它信息域,

struct node * next; //链表中指针域

}RecNode; //记录结点类型

typedef RecNode * LinkList ; //单链表用LinkList表示

void selectsort(linklist head)

{RecNode *p,*q,*s;

if(head->next)&&(head->next->next)

{p=head->next;//p指向当前已排好序最大元素的前趋

while (p->next)

{q=p->next;s=p;

while(q)

{if (q->key<s->key) s=q;

q=q->next;

}//endwhile

交换s结点和p结点的数据;

p=p->next;

}//endwhile

}//endif

}//endsort

21.写一个heapInsert(R,key)算法,将关键字插入到堆R中去,并保证插入R后仍是堆。提示:应为堆R增加一个长度属性描述(即改写本章定义的SeqList类型描述,使其含有长度域);将key先插入R中已有元素的尾部(即原堆的长度加1的位置,插入后堆的长度加1),然后从下往上调整,使插入的关键字满足性质。请分析算法的时间。

答:

#define n 100//假设文件的最长可能长度

typedef int KeyType; //定义KeyType 为int型

typedef struct node{

KeyType key; //关键字域

OtherInfoType info; //其它信息域,

}Rectype; //记录结点类型

typedef struct{

Rectype data[n] ; //存放记录的空间

int length;//文件长度

}seqlist;

void heapInsert(seqlist *R,KeyType key)

{//原有堆元素在R->data[1]~R->data[R->length],

//将新的关键字key插入到R->data[R->length+1]位置后,

//以R->data[0]为辅助空间,调整为堆(此处设为大根堆)

int large;//large指向调整结点的左右孩子中关键字较大者

int low,high;//low和high分别指向待调整堆的第一个和最后一个记录

int i;

R->length++;R->data[R->length].key=key;//插入新的记录

for(i=R->length/2;i>0;i--)//建堆

{

low=i;high=R->length;

R->data[0].key=R->data[low].key;//R->data[low]是当前调整的结点

for(large=2*low;large<=high;large*=2){

//若large>high,则表示R->data[low]是叶子,调整结束;

//否则令large指向R->data[low]的左孩子

if(large<high&&R->data[large].key<R->data[large+1].key)

large++;//若R->data[low]的右孩子存在

//且关键字大于左兄弟,则令large指向它

if (R->data[0].key<R->data[large].key)

{ R->data[low].key= R->data[large].key;

low=large;//令low指向新的调整结点

}

else break;//当前调整结点不小于其孩子结点的关键字,结束调整

}//endfor

R->data[low].key=R->data[0].key;//将被调整结点放入最终的位置上

}//end of for

}end of heapinsert

算法分析:

设文件长度为n,则该算法需进行n/2趟调整,总的时间复杂度与初建堆类似,最坏时间复杂度为O(nlgn),辅助空间为O(1).

22.写一个建堆算法:从空堆开始,依次读入元素调用上题中堆插入算法将其插入堆中。

答:

void BuildHeap(seqlist *R)

{

KeyType key;

R->length=0;//建一个空堆

scanf("%d",&key);//设MAXINT为不可能的关键字

while(key!=MAXINT)

{

heapInsert(R,key);

scanf("%d",&key);

}

}

23.写一个堆删除算法:HeapDelete(R,i),将R[i]从堆中删去,并分析算法时间,提示:先将R[i]和堆中最后一个元素交换,并将堆长度减1,然后从位置i开始向下调整,使其满足堆性质。

答:

void HeapDelete(seqlist *R,int i)

{//原有堆元素在R->data[1]~R->data[R->length],

//将R->data[i]删除,即将R->data[R->length]放入R->data[i]中后,

//将R->length减1,再进行堆的调整,

//以R->data[0]为辅助空间,调整为堆(此处设为大根堆)

int large;//large指向调整结点的左右孩子中关键字较大者

int low,high;//low和high分别指向待调整堆的第一个和最后一个记录

int j;

if (i>R->length)

Error("have no such node");

R->data[i].key=R->data[R->length].key;

R->length--;R->data[R->length].key=key;//插入新的记录

for(j=i/2;j>0;j--)//建堆

{

low=j;high=R->length;

R->data[0].key=R->data[low].key;//R->data[low]是当前调整的结点

for(large=2*low;large<=high;large*=2){

//若large>high,则表示R->data[low]是叶子,调整结束;

//否则令large指向R->data[low]的左孩子

if(large<high&&R->data[large].key<R->data[large+1].key)

large++;//若R->data[low]的右孩子存在

//且关键字大于左兄弟,则令large指向它

if (R->data[0].key<R->data[large].key)

{ R->data[low].key= R->data[large].key;

low=large;//令low指向新的调整结点

}

else break;//当前调整结点不小于其孩子结点的关键字,结束调整

}//endfor

R->data[low].key=R->data[0].key;//将被调整结点放入最终的位置上

}//end of for

}end of HeapDelete

24.已知两个单链表中的元素递增有序,试写一算法将这两个有序表归并成一个递增有序的单链表。算法应利用原有的链表结点空间。

答:

typedef struct node{

KeyType key; //关键字域

OtherInfoType info; //其它信息域,

struct node * next; //链表中指针域

}RecNode; //记录结点类型

typedef RecNode * LinkList ; //单链表用LinkList表示

void mergesort(LinkList la,LinkList lb,LinkList lc)

{RecNode *p,*q,*s,*r;

lc=la;

p=la;//p是la表扫描指针,指向待比较结点的前一位置

q=lb->next;//q是lb表扫描指针,指向比较的结点

while(p->next)&&(q)

if (p->next->key<=q->key)

p=p->next;

else {s=q;q=q->next;

s->next=p->next;p->next=s;//将s结点插入到p结点后

p=s;}

if (!p->next) p->next=q;

free(lb);

}

25.设向量A[0..n-1]中存有n个互不相同的整数,且每个元素的值均在0到n-1之间。试写一时间为O(n)的算法将向量A排序,结果可输出到另一个向量B[0..n-1]中。

答:

sort(int *A,int *B)

{//将向量A排序后送入B向量中

int i;

for(i=0;i<=n-1;i++)

B[A[i]]=A[i];

}

*26.写一组英文单词按字典序排列的基数排序算法。设单词均由大写字母构成,最长的单词有d个字母。提示:所有长度不足d个字母的单词都在尾处补足空格,排序时设置27个箱子,分别与空格,A,B...Z对应。

答:

#define KeySize 10 //设关键字位数d=10

#define Radix 27 //基数rd为27

typedef RecType DataType;//将队列中结点数据类型改为RecType类型

typedef struct node{

char key[KeySize]; //关键字域

OtherInfoType info; //其它信息域,

}RecType; //记录结点类型

typedef RecType seqlist[n+1];

void RadixSort(seqlist R)

{

LinkQueue B[Radix];

int i;

for(i=0;i<Radix;i++)//箱子置空

InitQueue(&B[i]);

for(i=KeySize-1;i>=0;i--){//从低位到高位做d趟箱排序

Distribute(R,B,i);//第KeySize-i趟分配

Collect(R,B);//第KeySize-i趟收集

}

}

void Distribute(seqlist R,LinkQueue B[], int j)

{//按关键字的第j个分量进行分配,初始时箱子为空

int i;

j=KeySize-j; // 确定关键字从低位起的位置

for(i=0;i<n;i++) //依次扫描R[i],将其装箱

if (R[i].key[j]-'A'>26)

EnQueue(&B[0],R[i]);//将第j位关键字位空格的记录入第0个队列

else EnQueue(&B[0],R[R[i].key[j]-'A'+1]);

}

void Collect(seqlist R,LinkQueue B[])

{

//依次将各非空箱子中的记录收集起来,本过程结束,各箱子都变空

int i,j;

for (j=0;j<Radix;j++)

while(!QueueEmpty(&B[j]))

R[i++]=DeQueue(&B[j]);//将出队记录依次输出到R[i]中

}