本篇技术博文摘要 🌟
- 排序算法题全面试炼
引言 📘
- 在这个变幻莫测、快速发展的技术时代,与时俱进是每个IT工程师的必修课。
- 我是盛透侧视攻城狮,一名什么都会一丢丢的网络安全工程师,也是众多技术社区的活跃成员以及多家大厂官方认可人员,希望能够与各位在此共同成长。
上节回顾
目录
8.3
8.3.1题
- 已知线性表按顺序存储,且每个元素都是不相同的整数型元素,设计把所有奇数移动到所有偶数前边的算法(要求时间最短,辅助空间最小)。
代码算法实现思路:
- 可采用基于快速排序的划分思想来设计算法,只需遍历一次即可,其时间复杂度为0(n),空间复杂度为0(1)。
- 假设表为L[1.n],基本思想是:先从前往后找到一个偶数元素L(i),再从后往前找到一个奇数元素 L(j),将二者交换;
- 重复上述过程直到 i 大于 j。
核心代码实现:
void move(ElemType A[],int len){
//对表A按奇偶进行一趟划分
int i=0,j=len-1; //i表示左端偶数元素的下标;j表示右端奇数元素的下标
while(i<j){
while(i<j&&A[i]%2!=0) i++; //从前往后找到一个偶数元素
while(i<j&&A[j]%2!=1) j--; //从后往前找到一个奇数元素
if (i<j){
Swap(A[i],A[j]); //交换这两个元素
i++; j--;
}
}
}
8.3.2题
- 试编写一个算法,使之能够在数组 L[1...n] 中找出第 k小的元素(即从小到大排序后处于第 k 个位置的元素)。
代码算法实现思路:
- 显然,本题最直接的做法是用排序算法对数组先进行从小到大的排序,然后直接提取L(k)便得到了第 k 小的元素,但其平均时间复杂度将达到 O(nlogzn)以上。
- 此外,还可采用小顶堆的方法,每次堆顶元素都是最小值元素,时间复杂度为O(n+klogn)。
- 综上我们可以介绍一个更精彩的算法,它基于快速排序的划分操作。
- 这个算法的主要思想如下:
- 从数组 L[1...n]中选择枢轴 pivot(随机或直接取第一个)进行和快速排序一样的划分操作后,表L[1.n]被划分为L[1.m-1]和L[m+1..n],其中L(m)=pivot。
- 讨论m 与k 的大小关系:
- 1)当 m=k时,显然pivot就是所要寻找的元素,直接返回pivot即可。
- 2)当m<k时,所要寻找的元素一定落在L[m+1.n]中,因此可对L[m+1..n]递归地查找第k-m 小的元素。
- 3)当 m>k时,所要寻找的元素一定落在 L[1..m-1]中,因此可对 L[1...m-1]递归地查找第k 小的元素。该算法的时间复杂度在平均情况下可以达到O(n),而所占空间的复杂度则取决于划分的方法。算法的实现如下:
核心代码实现:
int kth_elem(int a[],int low,int high,int k){
int pivot=a[low];
int low_temp=low; //由于下面会修改low与high,在递归时又要用到它们
int high_temp=high;
while(low<high){
while(low<high&&a[high]>=pivot)
--high;
a[low]=a[high];
while(low<high&&a[low]<=pivot)
++low;
a[high]=a[low];
}
a[low]=pivot;
//上面为快速排序中的划分算法
//以下是本算法思想中所述的内容
if(low==k) //由于与k相同,直接返回pivot元素
return a[low];
else if(low>k) //在前一部分表中递归寻找
return kth_elem(a,low_temp,low-1,k);
else //在后一部分表中递归寻找
return kth_elem(a,low+1,high_temp,k);
}
8.3.3题
- 荷兰国旗问题:设有一个仅由红、白、蓝三种颜色的条块组成的条块序列,存储在一个顺序表中,请编写一个时间复杂度为 O(n)的算法,使得这些条块按红、白、蓝的顺序排好,即排成荷兰国旗图案。请完成算法实现:
代码算法实现思路:
- 我们可以顺序扫描线性表,将红色条块交换到线性表的最前面,蓝色条块交换到线性表的最后面。为此,设立三个指针,其中,j为工作指针,表示当前扫描的元素, i以前的元素全部为红色, k以后的元素全部为蓝色。
- 根据j所指示元素的颜色,决定将其交换到序列的前部或尾部。初始时i=0, k=n-1,算法的实现如下:
核心代码实现:
typedef enum{RED,WHITE,BLUE} color; //设置枚举数组
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;
//红色,则和i交换
case WHITE: j++;break;
case BLUE: Swap(a[j],a[k]);k--;
//蓝色,则和k交换
//这里没有j++语句,以防止交换后a[j]仍为蓝色
}
}
}
补充:
- 例如,将元素值正数、负数和零排序为前面都是负数,接着是0,最后是正数,也用同样的方法。
8.3.4题
代码算法实现思路:
- 我们可以将最小的Ln/21个元素放在A1中,其余的元素放在A2中,分组结果即可满足题目要求。仿照快速排序的思想,基于枢轴将n个整数划分为两个子集。
- 根据划分后枢轴所处的位置 i分别处理:
- ① 若i=Ln/2],则分组完成,算法结束。
- ② 若i<Ln/2],则枢轴及之前的所有元素均属于 A,继续对i之后的元素进行划分。
- ③ 若i>Ln/2],则枢轴及之后的所有元素均属于A2,继续对i之前的元素进行划分。
- 基于该设计思想实现的算法,无须对全部元素进行全排序,其平均时间复杂度是O(n),空间复杂度是 O(1)。
核心代码实现:
int setPartition(int a[], int n){
int pivotkey, low=0,low0=0,high=n-1,high0=n-1,flag=1,k=n/2,i;
int s1=0,s2=0;
while(flag) {
pivotkey=a[low]; //选择枢轴
while(low<high) { //基于枢轴对数据进行划分
while(low<high && a[high]>=pivotkey) --high;
if(low!=high) a[low]=a[high];
while(low<high && a[low]<=pivotkey) ++low;
if(low!=high) a[high]=a[low];
}
a[low]=pivotkey; //end of while(low<high)
if(low==k-1) //若枢轴是第n/2小的元素,划分成功
flag=0;
else{ //是否继续划分
if(low<k-1){
low0=++low;
high=high0;
}
else{
high0=--high;
low=low0;
}
}
}
for(i=0;i<k;i++) s1+=a[i];
for(i=k;i<n;i++) s2+=a[i];
return s2-s1;
}
8.4
8.4.1题
- 编写一个算法,在基于单链表表示的待排序关键字序列上进行简单选择排序。
代码算法实现思路:
- 每趟在原始链表中摘下关键字最大的结点,把它插入结果链表的最前端。
- 由于在原始链表中摘下的关键字越来越小,在结果链表前端插入的关键字也越来越小,因此最后形成的结果链表中的结点将按关键字非递减的顺序有序链接。
核心代码实现:
void selectSort(LinkedList& L){
//对不带头结点的单链表L执行简单选择排序
LinkNode *h=L,*p,*q,*r,*s;
L=NULL;
while(h!=NULL){ //持续扫描原链表
p=s=h;q=r=NULL;
//指针s和r记忆最大结点和其前驱;p为工作指针,q为其前驱
while(p!=NULL){ //扫描原链表寻找最大结点s
if(p->data>s->data){s=p;r=q;} //找到更大的,记忆它和它的前驱
q=p;p=p->link; //继续寻找
}
if(s==h)
h=h->link; //最大结点在原链表前端
else
r->link=s->link; //最大结点在原链表表内
s->link=L;L=s; //结点s插入结果链前端
}
}
8.4.2题
- 试设计一个算法,判断一个数据序列是否构成一个小根堆。
代码算法实现思路:
- 将顺序表L[1..n]视为一个完全二叉树,扫描所有分支结点
- 遇到孩子结点的关键字小于根结点的关键字时返回false,扫描完后返回true
核心代码实现:
bool IsMinHeap(ElemType A[],int len){
if(len%2==0){ //len为偶数,有一个单分支结点
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{ //len为奇数时,没有单分支结点
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.4.3题
- 现有 n (n> 100000)个数保存在一维数组 M 中,需要查找 M 中最小的 10 个数。请回答下列问题。1)设计一个完成上述查找任务的算法,要求平均情况下的比较次数尽可能少,简述其算法思想(不需要编程实现)。2)说明你所设计的算法平均情况下的时间复杂度和空间复杂度。
代码算法实现思路:
- 方法 1:
- 先创建一个长度为 10 的数组
A
,并初始化为int
类型能表示的最大值。- 遍历集合
M
中的每个元素s
,如果s
小于A[9]
(数组中最大的元素 ),就将s
插入数组A
并重新排序,保证数组A
中始终保存当前最小的 10 个数。- 最后输出数组
A
中的元素。- 方法 2:
- 创建一个大根堆
H
,并先插入 10 个int
类型能表示的最大值。- 遍历集合
M
中的每个元素s
,如果s
小于堆顶元素(堆中最大的元素 ),就删除堆顶元素并插入s
,维持堆的性质。- 最后将堆中的元素依次取出并输出,得到最小的 10 个数。
8.5
8.5.1题
void cmpCountSort(int a[],int b[],int n){
int i,j,*count;
count=(int *)malloc(sizeof(int)*n);
//C++语言: count=new int[n];
for(i=0;i<n;i++) count[i]=0;
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(a[i]<a[j]) count[j]++;
else count[i]++;
for(i=0;i<n;i++) b[count[i]]= a[i];
free(count); //C++语言: delete count;
}
代码算法实现思路:
- cmpCountsort算法基于计数排序的思想,对序列进行排序.cmpcountsort算法遍历数组的元素, count数组记录比对应待排序数组元素下标大的元素个数,
- 例如, count[1]-3的意思数组a中有三个元素比a[1]小,即a[1]是第四大元素, a[1]的正确位置应是b[3].
核心代码实现:
- 1)排序结果为b[6]=(-10,10,11,19,25,25}。
for(i=0;i<n-1;i++)和for (j=i+1;j<n;j++)
- 可知,在循环过程中,每个元素都与它后面的所有元素比较一次(即所有元素都两两比较一次),比较次数之和为(n-1)+(n-2)+…+1,所以总比较次数是n(n-1)/2。
- 3)不是。需要将程序中的 if 语句修改为:
if(a[i]<=a[j]) count[j]++;else count[i]++;
- 若不加等号,两个相等的元素比较时,前面元素的count值会加1,则导致原序列中靠前的元素在排序后的序列中处于靠后的位置。
8.6
8.6.1题
- 设顺序表用数组 A1表示,表中元素存储在数组下标 1-m+n 的范围内,前 m 个元素递增有序,后n个元素递增有序,设计一个算法,使得整个顺序表有序。
代码算法实现思路:
- 将数组A[1..m+n]视为一个已经过m趟插入排序的表,则从m+1 趟开始,将后 n 个元素依次插入前面的有序表中。
核心代码实现:
void Insert_Sort(ElemType A[],int m,int n){
int i,j;
// 外层循环,遍历从m+1到m+n的元素,依次将这些元素插入有序表
for(i=m+1;i<=m+n;i++){
// 将当前要插入的元素A[i]复制到A[0],A[0]作为哨兵,方便后续比较和移动元素
A[0]=A[i];
// 内层循环,从后往前比较,找到插入位置
for(j=i-1;A[j]>A[0];j--)
// 将比A[0]大的元素往后移动一位
A[j+1]=A[j];
// 将元素插入到合适位置
A[j+1]=A[0];
}
}
//时间复杂度由 m 和n共同决定,在最坏情况下元素的比较次数为O(mn),而元素移动的次数为O(mn),所以时间复杂度为O(mn)
补充:
此外,本题也可采用归并排序,将A[1.m]和A[m+1..m+n]视为两个待归并的有序子序列,去的时间复杂度为O(m+n),空间复杂度为O(m+n)
8.6.2题
- 设有一个数组中存放了一个无序的关键序列 Ki,K2,…,K。现要求将 K,放在将元素排序后的正确位置上,试编写实现该功能的算法,要求比较关键字的次数不超过 n.
代码算法实现思路:
- 以 K为枢轴进行一趟快速排序。
- 将快速排序算法改为以最后一个元素为枢轴,先从前往后,再从后往前。
核心代码实现:
int Partition(ElemType K[],int n){
int i=1,j=n; //设置两个交替变量初值分别为1和n
ElemType pivot=K[j]; //枢轴
while(i<j){ //循环跳出条件
while(i<j&&K[i]<=pivot)
i++; //从前往后找比枢轴大的元素
if(i<j)
K[j]=K[i]; //移动到右端
while(i<j&&K[j]>=pivot)
j--; //从后往前找比枢轴小的元素
if(i<j)
K[i]=K[j]; //移动到左端
} //while
K[i]=pivot; //枢轴存放在最终位置
return i; //返回存放枢轴的位置
}
欢迎各位彦祖与热巴畅游本人专栏与技术博客
你的三连是我最大的动力
点击➡️指向的专栏名即可闪现
➡️ 永恒之心蓝队联纵合横防御
➡️逆向软件破解工程
➡️红帽高级工程师
➡️红帽系统管理员