C++面试题(算法基础-排序算法)
Posted DP视觉说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++面试题(算法基础-排序算法)相关的知识,希望对你有一定的参考价值。
1、常用的排序算法
以下是一些最基本的排序算法。虽然在C++里可以用std::sort()进行排序,而且刷题一般也不需要自己写排序算法,但是熟悉各种排序算法 可以加深自己对算法的了解呀,以及解出由这些排序算法引申出来的算法。如下图所示:
各算法的复杂度如下图所示:
其中,稳定表示:如果a原本在b前面,而a=b,则排序完之后a还是在b前面。不稳定则表示可能会交换a和b的位置。时间复杂度表示对排序数据的总操作次数,反应了n变化时,耗费时间的变化。空间复杂度则表示算法会占用的存储空间的度量。
(1)冒泡排序
冒泡排序是一种简单的排序算法,重复从左到右两两比较,每次都将本次循环的最值移动到最后。动画如下:
代码实现:
void BubbleSort(int arr[],int n){ //冒泡排序接口
//flag标志位,count1和count2是用来计数的
int flag = 1, count1 = 0, count2 = 0;
//开始遍历,i代表是第几轮,j代表当前遍历到了第几个
for(int i=0;i<n && flag;i++){
flag = 0;
for(int j = 0;j<n-1-i;j++){
count1 ++;
if(arr[j] > arr[j+1]){
count2++;
flag = 1;
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
(2)选择排序
选择排序是比较直观的一种排序方法,其工作原理就是在未排序的序列中找到最小或最大的元素,防止本次遍历的起始位置,重复直到全部排序为止。算法示意图如下:
代码实现:
void SelectSort(int arr[],int n){
int min;
for(int i=0;i<n;i++){
min = i;
for(int j = i+1;j<n;j++){
if(arr[j] < arr[min]){
min = j;
}
}
if(i!=min){
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
算法分析:选择排序是最稳定的排序算法之一,因为无论什么数据进去,时间复杂度都是O(n2),所以数据规模越小越有利,而且不占用空间。
(3)插入排序
插入排序其原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。动画如图所示:
一般来说插入排序都采用in-place在数组上实现,具体算法描述如下:
从第一个元素开始,该元素可以认为已排序元素;
取出下一个元素,在已排序的元素序列中从后向前扫描;
如果该元素(已排序)大于新元素,将该元素移到下一位置。
重复3,知道找到已排序的元素小于或等于新元素的位置所在;
将新元素插入到该位置后重复2-5。
代码实现:
void InsertSort(int arr[],int n){
//temp用来暂时存放
int temp;
//开始遍历
for(int i=1;i<n;i++){
if (arr[i-1] > arr[i]){
temp = arr[i];
for(int j = i-1; arr[j] > temp;j--){
arr[j+1] = arr[j];
}
arr[j+1] = temp;
}
}
}
算法分析:采用in-place的排序算法,空间上只用了O(1)的额外空间的排序,因此在后向扫描过程中,需要反复的把已排序元素逐步向后挪位,为新元素提供位置。
(4)希尔排序
这个排序算法首次突破了O(n2)的时间复杂度,改进了简单的插入排序,其特点在于:它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。动画如下:
算法描述:
首先将整个待排序的记录序列分割为若干个子序列,分别进行插入排序:
选择一个增量序列t1、t2、、、tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k躺排序;
每次排序,根据对应的增量ti,将待排序列分割为若干长度为m的子序列,分别对各子表进行直接插入排序。仅当增量因子为1时,整个序列作为一个表来处理,表长度就是整个序列的长度。
代码实现:
void ShellSort(int arr[], int n){
//gap是增量大小
int temp,gap = n;
do {
gap = gap/3 + 1;
for(int i = gap; i < n; i++){
if (arr[i-gap] > arr[i]){
temp = a[i];
for(int j = i-gap; a[j] > temp; j-=gap){
a[j+gap] = a[j];
}
a[j+gap] = temp;
}
}
}while( gap > 1 );
}
算法分析:希尔排序的核心在于间隔序列的设定,以及如何调整。
(5)归并排序
归并排序算法是建立在归并操作上的一种方法,说白了就是采用分治法,将有序的子序列进行排序然后合并,得到最终的有序序列。核心在于,先对子序列排序,合并后再整体排序。动画如下:
算法描述:
将长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个有序序列。
代码实现:
//递归版本
//
void Merging(int *list1,int list1_size,int *list2,int list2_size){
int temp[100]; //自己定义
int i,j,k;
i = j = k = 0;
while(i<list1_size && j<list2_size){
if(list1[i] < list2[j]){ //如果要实现从大到小排序 则修改此处的符号即可
temp[k++] = list1[i++];
}
else {
temp[k++] = list2[j++];
}
}
while(i<list1_size){
temp[k++] = list1[i++];
}
while(j<list2_size){
temp[k++] = list2[j++];
}
for(i = 0;i<(list1_size + list2_size);i++){
list1[i] = temp[i];
}
}
//归并排序的入口,k[]为待排序序列,n为其长度
void MergeSort(int k[],int n){
if(n<=1) return;
//分成左右两子序列
int *list1 = k;
int list1_size = n/2;
int *list2 = k+n/2;
int list2_size = n - list1_size;
//对左右子序列分别排序
MergeSort(list1,list1_size);
MergeSort(list2,list2_size);
//放一块排序
Merging(list1,list1_size,list2,list2_size);
}
//迭代版本,c版本
void MergeSort(int k[],int n){
int left_max,right_min,right_max;
int *temp = (int *)malloc(sizeof(int)*n);
for(int i=1;i<n;i*=2){ //i 是 步长
for(int left_min = 0;left_min < n - i; left_min = right_max){
right_min = left_max = left_min+i;
right_max = left_max + i;
if(right_max>n){
right_max = n;
}
int next = 0;
while(left_min < left_max && right_min <right_max){
if(k[left_min] < k[right_min]){
temp[next++] = k[left_min++];
}
else{
temp[next++] = k[right_min++];
}
}
while(left_min < left_max){
k[--right_max] = k[--left_max];
}
while(next > 0){
k[--right_min] = temp[--next];
}
}
}
}
(6)快速排序
快排是一种稳定的排序算法,其性能不受到输入数据的影响,与选择排序一样,但表现比选择排序要好。因为其始终都是O(nlogn)的时间复杂度,代价是需要额外内存空间。动画如下:
算法描述:
快排也会采用分治法,将一个串list分为两个子串。具体算法如下:
从数列中挑一个元素,称为基准(pivot);
重新排序数列,所有元素比基准值小的摆在前面,比基准值大的放后面,相同的随便。退出之后,基准将处于相对中间的位置,其左右便做好了分区。
递归地把小于基准值元素的子数列和大于基准值元素的子数列进行排序。
代码实现:
void quick_sort(vector<int> &nums, int l, int r) {
if (l + 1 >= r) {
return;
}
int first = l, last = r - 1, key = nums[first];
while (first < last){
while(first < last && nums[last] >= key) {
--last;
}
nums[first] = nums[last];
while (first < last && nums[first] <= key) {
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quick_sort(nums, l, first);
quick_sort(nums, first + 1, r);
}
void quick_sort( vector<int> &nums, int l, int r ){ //输入待排序序列 和左边界,右边界
if( l + 1 >= r ){
return;
}
int first = l, last = r - 1, key = nums[first]; //key就是pivot
while( first < last ){ //循环
while( first < last && nums[last] >= key ){ //
--last;
}
nums[first] = nums[last];
while( first < last && nums[first] <= key ){
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quick_sort( nums, l, first );
quick_sort( nums, first + 1, r );
}
(7)堆排序
堆排序基础是堆这种数据结构,堆是近似于一种完全二叉树的结构,并同时满足堆积的性质,即子节点的键值或索引总是小于(小顶堆)或者大于(大顶堆)他的父节点,这里用的是大顶堆。原理详见:https://www.cnblogs.com/MOBIN/p/5374217.html
(8)计数排序
计数排序并不基于比较方法,其核心在于将输入的数据值转换为键值key存储在额外开辟的数组空间里,考虑到可行性,要求数据必须在有确定范围之内。动画如下:
算法描述:
找出待排序的数组中最大最小元素;
统计数组中每个元素出现的次数,存入数组c的第i项;
对所有的奇数累加(从C中第一个元素开始,每一项和前一项相加);
反向填充目标数组,将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
算法分析:计数排序是一个稳定的排序算法,当输入的元素是n个0到k之间的整数时,时间复杂度为O(n+K),空间复杂度也是O(n+K),其排序速度快于任何比较排序算法,k越小,数值越集中,速度越快。
(9)桶排序
桶排序是计数排序的升级版,其利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序的原理:假设输入数据服从均匀分布,将数据分到有限的桶里,每个桶再分别排序(可以使用别的排序方法或者递归桶排序)。算法如图:
算法描述:
设置一个定量的数组当做空桶;
遍历输入数据,并且把数据一个一个放进对应桶里;
对每个不是空的桶进行排序,算法自定,可用别的排序算法也可递归桶排序;
从不是空的桶里把排序好的数据拼起来。
算法分析:桶排序最好的情况下使用,线性时间O(n),桶排序的时间复杂度取决于各个桶之间数据进行排序的时间复杂度。桶划分的越细,各个桶内排序越快,但空间占用越大。
(10)基数排序
基数排序是按照低位先排序,然后收集,再按照高位排序,再收集,知道最高位排完。算法如图:
算法描述:
取得数组中的最大数,并取得位数;
arr为原始数组,从最低位开始取每个位组成radix数组;
对radix进行计数排序。
算法分析:
基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。
本文动画图来自博客:
https://www.cnblogs.com/onepixel/articles/7674659.html
以上是关于C++面试题(算法基础-排序算法)的主要内容,如果未能解决你的问题,请参考以下文章
Java八股文面试题 基础篇 -- 二分查找算法冒泡排序选择排序插入排序希尔排序快速排序
Java八股文面试题 基础篇 -- 二分查找算法冒泡排序选择排序插入排序希尔排序快速排序