8_3
01、在使用非递归方法实现快速排序时,通常要用一个栈来记忆待排序区间的两个端点。能否用队列 来实现这个栈,为什么?
答:能
栈的作用是在处理一个子区间时,保存另一个子区间的上界和下界(排序过程可能产生新的左右子区间),待该区间处理完后,再从栈中取出另一个子区间的 边界。用队列也可以实现,只是处理子区间的顺序有所变动。
(栈保存相当于先处理最小的区间,再一层层返回,直至大区间;而队列则是逐层逐个区间依次处理)
02、编写双向冒泡排序算法,在正反两个方向交替进行分扫描,即第一趟把关键字最大的元素放在序列的最后面,第二趟把关键字最小的元素放在序列的最前面,如此反复进行。
//双向起泡
//思路:
// 第一趟把最大的元素放在序列的最后面 即从前往后扫描 成功后 修改上界
// 第二趟把最小的元素放在序列的最前面 即从后往前扫描 成功后 修改下界
void D_BubbleSort(ElemType a[],int n)
{
int low=0,high=n-1;
int i,j;
bool flag=true;//记录每趟是否交换的标志
while(low<high&&flag)
{
flag=false;
//将最大的元素放在最后面
for(i=low;i<high;i++)
if(a[i]>a[i+1])
{
swap(a[i],a[i+1]);
flag=true;//交换 则置flag为true
}
high--;//修改上界
//将最小的元素放在最前面
for(i=high;i>low;i--)
if(a[i-1]>a[i])
{
swap(a[i-1],a[i]);
flag=true;
}
low++;//修改下界
}
}
03、已知线性表按顺序存储,且每个元素都是不相同的的整数型元素,设计把所有奇数移动到所有偶数前边的算法(要求时间最少,辅助空间最少)
//双指针
//low从前往后扫描 high从后往前扫描
//如果a[low]是奇数则low++ a[high]是偶数则high--
//若扫描过程中 出现a[low]是偶数 a[high]是奇数 则交换
void move(ElemType a[],int n)
{
int low=0,high=n-1;
while(low<high)
{
while(low<high&&a[low]%2==1) low++;//从前往后找到第一个偶数
while(low<high&&a[high]%2==0) high--;//从后往前找到第一个奇数
if(low<high)
{
swap(a[low],a[high]);
low++;high--;//修改指针 继续扫描
}
}
}
04、试重新编写考点精析中的快速排序的划分算法,使之每次选取的枢轴值都是随机地从当前子表中选择的。
//只需在原来代码前加两行
int rand_pos=low+rand%(high-low+1);
swap(a[rand_pos],a[low]);
05、试编写一个算法,使之能够在数组L(1..n)中找出第k小的元素(即从小到大排序后处于第k个位置的元素)。
#include <iostream>
using namespace std;
typedef int ElemType;
const int N=10010;
int L[N],n,k;
//找出L[1 2 3... n]中第k小的元素
//直接想法:直接对L[]中元素排序 返回L[k]
//2--王道
//思路:蕴含快排+分治
//选取一个枢轴元素pivot 使用partition找到枢轴元素在顺序表中的位置pos
//如果 pos==k 则返回pivot
//如果 pos>k 则说明要找的元素在pos左边 即L[1...pos]里 于是在这段区间找第k个位置的元素
//如果 pos<k 则说明要找的元素在pos右边 即L[pos... n]里 于是在这段区间找第k-pos个位置的元素即可
ElemType search_k_th(ElemType L[],int low,int high,int k)
{
int pivot=L[low];
int low_pos=low;
int high_pos=high;//由于下面会修改low 与 high 故先保存 以便递归时使用
while(low<high)
{
while(low<high&&L[high]>=pivot) high--;//找到比pivot小的元素 放在左边
L[low]=L[high];
while(low<high&&L[low]<=pivot) low++;//找到比pivot大的元素 放在右边
L[high]=L[low];
}
L[low]=pivot;//将pivot放在最终位置上
if(low==k) //low与k相等 直接返回
return L[low];
else if(low>k)//在前一部分找第k个元素
return search_k_th(L,low_pos,low-1,k);
else
return search_k_th(L,low+1,high_pos,k);//因为返回的是某个位置的元素 所以这里参数还是传入k 并未k-low
}
int main()
{
cout<<"输入元素个数:";
cin>>n;
for(int i=1;i<=n;i++) cin>>L[i];
cout<<"初始元素序列为:";
for(int i=1;i<=n;i++) cout<<L[i]<<" ";
cout<<endl;
cout<<"要找的元素序号:";
cin>>k;
int res=search_k_th(L,1,n,k);
cout<<"第"<<k<<"小的元素为:"<<res<<endl;
return 0;
}
//该算法 时间复杂度为O(n)
06、荷兰国旗问题:设有一个仅由红、白、蓝三种颜色的条块组成的条块序列,请编写一个时间复杂度为O(n)的算法,使得这些条块按红、白、蓝的顺序排好,即排成荷兰国旗图案。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//荷兰国旗问题
//思路:设置三个指针i,j,k;起初i指向线性表第一个元素 k指向线性表最后一个元素 j为工作指针
//j从头到尾扫描一遍线性表
//如果j为红色 则与i指针所指颜色交换(交换到前面);j为蓝色 则与k指针所指颜色交换(交换到后面);j为白色则j++
typedef enum{
red,
white,
blue
}color;
color a[100];
void swap(color &m,color &n)
{
color temp=m;
m=n;
n=temp;
}
void flag_Arrange(color a[],int n)
{
int i=0,j=0,k=n-1;
while(j<=k)
{
switch(a[j])
{
case red:
swap(a[i],a[j]);
i++,j++;
break;
case white:
j++;
break;
case blue:
swap(a[j],a[k]);
k--;//这里不能j++ 因为a[i] 和 a[j]交换后仍可能为blue
break;
}
}
}
int main()
{
int n;//元素个数
char input[20]={0};
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",input);
if(strcmp(input,"red")==0)
{
a[i]=red;
}else if(strcmp(input,"white")==0)
{
a[i]=white;
}else if(strcmp(input,"blue")==0)
{
a[i]=blue;
}
memset(input,0,sizeof(input));
}
flag_Arrange(a,n);
for(int i=0;i<n;i++)
{
switch(a[i])
{
case red:
printf("red ");
break;
case white:
printf("white ");
break;
case blue:
printf("blue ");
break;
}
}
}
07、
#include <iostream>
using namespace std;
const int N=10010;
int A[N],A1[N],A2[N];//A1存放A中更大的元素 A2存放A中更小的元素
//思路:
//要使|n1-n2|最小 同时|S1-S2|最大 则将排序后的前一半元素放入A2(其中元素更小) 后一半元素放入A1(其中元素更大)
//当n是偶数时刚好一边一半 当n是奇数时则前n/2个元素放入A2(向下取整) 剩余的所有元素放入A1
void Arrange(int A[],int n,int low,int high,int A1[],int A2[])
{//n代表元素个数 low初始为0 high为n-1
int pos=n/2;
int flag=1;//标志 若完成了划分 则置flag为0
int low0=0,high0=n-1;//初始时 考察范围为[0,n-1]
//用快排的思想找位置
while(flag)
{
int pivot=A[low];
while(low<high)
{
while(low<high&&A[high]>=pivot) high--;//从左边找比pivot小的元素 找到放到右边
A[low]=A[high];
while(low<high&&A[low]<=pivot) low++;//从右边找比pivot大的元素 找到放在左边
A[high]=A[low];
}
A[low]=pivot;
//出while后 pivot的位置就定了
if(low==pos-1)//如果low的位置是 目标位置
{
flag=0;//则置flag为0 结束循环
}else if(low<pos-1)//若low比目标位置更小 则只需考察low之后的元素
{//此时 快排划分的范围变成了[low+1,high0]
low0=++low;//修改指针low 为low+1 同时保存low0为下一次划分做准备 因为下一次是更小的范围
high=high0;//修改指针high 指向high0
}else//若low比目标位置更大 则只需考虑low之前的元素
{//此时 快排划分的范围变成[low0,high-1]
high0=--high;//修改指针high为high-1 同时改变high0为下一次划分做准备 下一次一定是更小的范围
low=low0;//修改指针low 指向low0
}
}
for(int i=0;i<pos;i++) A2[i]=A[i];
for(int k=0,i=pos;i<n;i++) A1[k++]=A[i];
}
int main()
{
int n;
cout<<"待划分元素个数:";
cin>>n;
for(int i=0;i<n;i++) cin>>A[i];
cout<<"A中元素为:";
for(int i=0;i<n;i++) cout<<A[i]<<" ";
cout<<endl;
Arrange(A,n,0,n-1,A1,A2);
cout<<"A1表中元素为:";
if(n&1)
{
for(int i=0;i<=n/2;i++) cout<<A1[i]<<" ";
}else
{
for(int i=0;i<n/2;i++) cout<<A1[i]<<" ";
}
cout<<endl;
cout<<"A2表中元素为:";
for(int i=0;i<n/2;i++) cout<<A2[i]<<" ";
return 0;
}
8_4
04、编写一个算法,在基于单链表表示的待排序关键字序列上进行简单选择排序。
//基于单链表的简单选择排序
//1---无需断链 每次找到后交换data值
void select_LinkSort(LinkList L)
{
LNode *p=L->next;//指向首元素结点
LNode *min,*q;
int temp;//保存临时值
while(p)
{
min=p;
q=p->next;
while(q)//寻找最小元素结点
{
if(q->data<min->data)
min=q;
q=q->next;
}
//找到最小元素结点后 交换两个结点的数据域
if(min!=p)
{
temp=min->data;
min->data=p->data;
p->data=temp;;
}
p=p->next;
}
}
//2---断链 每次找到单链表的最大结点 然后将其头插入链表中
LinkList select_LinkSort_2(LinkList L)
{
LNode *h=L->next,*pre,*p,*maxpre,*max;
L->next=NULL;//将L从头结点断开 方便头插
//直到h扫描到链表尾 即NULL
while(h)
{
pre=maxpre=NULL,p=max=h;
//找到最大元素结点
while(p)
{
if(p->data>max->data)
{
max=p;
maxpre=pre;
}
pre=p;
p=p->next;
}
if(max==h)//最大结点 为首元素结点
h=h->next;//直接让h后移
else//否则 将该元素结点摘除 保证链表连接
maxpre->next=max->next;
//头插
max->next=L->next;
L->next=max;
}
return L;
}
05、试设计一个算法,判断一个数据序列是否构成一个小根堆。
//判断一个数据序列是否构成小根堆(用顺序表存储堆 且视为完全二叉树)
//思路:与建堆类似 判断len/2的左右孩子(如果有的话) 然后依次上升到第一个位置
bool Judge_minHeap(ElemType a[],int len)
{
if(len%2==0)//结点个数为偶数 有一个单分支结点
{
if(a[len/2]>a[len])
return false;
for(int i=len/2-1;i>=1;i--)
{
if(a[i]>a[2*i]||a[i]>a[2*i+1])//若父母结点大于左孩子或者右孩子结点
return false;//则不是小根堆
}
}else//结点个数为奇数 则每个分支节点都有左右孩子
{
for(int i=len/2;i>=1;i--)
{
if(a[i]>a[2*i]||a[i]>a[2*i+1])
return false;
}
}
return true;
}
8_6
02、
//1--插入排序
void Insert_Sort(ElemType a[],int m,int n)
{
int i,j;
for(i=m+1;i<=m+n;i++)
{
a[0]=a[i];
for(j=i-1;a[j]<a[0];j--)
a[j+1]=a[j];
a[j+1]=a[0];
}
}
//最坏情况下 元素比较次数O(mn) 移动次数O(mn) 时间复杂度O(mn)
//空间复杂度 O(1)
//2--归并排序
ElemType *b=(ElemType *)malloc((m+n+1)*sizeof(ElemType));
void MergeSort(ElemType a[],int m,int n)
{
int i,j,k;
for(int k=0;k<m+n;k++) b[k]=a[k];
for(i=0,j=m,k=0;i<m&&j<n;k++)
{
if(b[i]<=b[j])
a[k]=b[i++];
else
a[k]=b[j++];
}
while(i<m) a[k++]=b[i++];
while(j<n) a[k++]=b[j++];
}
//时间复杂度:O(m+n) 空间复杂度:O(m+n)
03、
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
//计数排序
void Count_Sort(ElemType a[],int n)//n表示有n个元素
{
ElemType *b=(ElemType *)malloc((n+1)*sizeof(ElemType));
int cnt=0;//记录小于关键码的元素个数
int i,j;//工作指针
for(i=0;i<n;i++)
{
int key=a[i];
for(j=0;j<n;j++)
{
if(a[j]<key)
cnt++;
}
b[cnt]=key;
cnt=0;//一轮循环后 cnt置0
}
for(i=0;i<n;i++) a[i]=b[i];//拷贝回原数组
}
int main()
{
int n;
scanf("%d",&n);
int a[n];
printf("排序前:");
getchar();
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
getchar();
}
printf("\n");
Count_Sort(a,n);
printf("排序后:");
for(int i=0;i<n;i++) printf("%d ",a[i]);
return 0;
}
//比较次数 n²
//简单选择排序比本排序号 简单选择排序只比较 n*(n-1)/2
//若限制任意两个记录只比较一次
const int N=10010;
int a[N],b[N],s[N];//s[N]作为计数器
void Count_Sort_2(ElemType a[],int n)
{
int i,j;
for(i=0;i<n;i++)
{
for(j=i+1;j<n;j++)
{
if(a[i]<a[j])//若当前元素比后序扫描的元素小
s[a[j]]++; //则让扫描到的元素的计数域+1
else
s[a[i]]++;//否则 让当前元素的计数域+1
}
b[s[a[i]]]=a[i];
}
for(int i=0;i<n;i++) a[i]=b[i];
04、设有一个数组中存放了一个无序的关键序列K1,K2,...Kn。现要求将Kn放在将元素排序后的正确位置上,试编写实现该功能的算法,要求比较关键字的次数不超过n。
//以Kn为枢轴元素进行一次快排
int Partition(ElemType a[],int high,int low)
{
low=0,high=n;
int pivot=a[high];
while(low<high)
{
while(low<high&&a[low]<=pivot) low++;//从前往后找比pivot大的元素 找到则交换
a[high]=a[low];
while(low<high&&a[high>=pivot]) high--;//从后往前找比pivot小的元素 找到则交换
a[low]=a[high];
}
a[low]=pivot;//将枢轴元素放在最终位置上
return low;
}