六、鸡尾酒排序/双向冒泡排序
1)算法简介
鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。他可以得到比冒泡排序稍微好一点的效能,原因是冒泡排序只从一个方向进行比对(由低到高),每次循环只移动一个项目。
2)算法描述和分析
1、依次比较相邻的两个数,将小数放在前面,大数放在后面;
2、第一趟可得到:将最大数放到最后一位。
3、第二趟可得到:将第二大的数放到倒数第二位。
4、如此下去,重复以上过程,直至最终完成排序。
鸡尾酒排序最糟或是平均所花费的次数都是O(n^2),但如果序列在一开始已经大部分排序过的话,会接近O(n)。
最差时间复杂度O(n^2)
最优时间复杂度O(n)
平均时间复杂度O(n^2)
3)算法图解
4)算法代码
void CocktailSort(int *a,int nsize)
{
int tail=nsize-1;
for (int i=0;i<tail;)
{
for (int j=tail;j>i;--j) //第一轮,先将最小的数据排到前面
{
if (a[j]<a[j-1])
{
int temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
}
}
++i; //原来i处数据已排好序,加1
for (j=i;j<tail;++j) //第二轮,将最大的数据排到后面
{
if (a[j]>a[j+1])
{
int temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
tail--; //原tail处数据也已排好序,将其减1
}
}
5)考察点,重点和频度分析鸡尾酒排序在博主印象中出现的频度也不高,用到它的算法题大题很少,选择填空出现的话多以双向冒泡排序的名称出现,注意注意时间空间复杂度,理解理解算法应该问题就不大了。
6)笔试面试例题
考点基本类似冒泡排序,请参考《数据结构基础 排序算法(一) 概念篇》最后一节。
详址:http://blog.csdn.net/u013630349/article/details/47211257
七、快速排序
恩,重头戏开始了,快速排序是各种笔试面试最爱考的排序算法之一,且排序思想在很多算法题里面被广泛使用。是需要重点掌握的排序算法。1)算法简介
快速排序是由东尼·霍尔所发展的一种排序算法。其基本思想是基本思想是,通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
2)算法描述和分析
快速排序使用分治法来把一个串(list)分为两个子串行(sub-lists)。步骤为:
1、从数列中挑出一个元素,称为 "基准"(pivot),
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
算法伪代码描述:
function quicksort(q)
var list less, pivotList, greater
if length(q) ≤ 1 {
return q
} else {
select a pivot value pivot from q
for each x in q except the pivot element
if x < pivot then add x to less
if x ≥ pivot then add x to greater
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。最差时间复杂度,O(n^2)
最优时间复杂度,O(n log n)
平均时间复杂度,O(n log n)
最差空间复杂度,根据实现的方式不同而不同
3)算法图解
快速排序会递归地进行很多轮,其中每一轮称之为快排的partition算法,即上述算法描述中的第2步,非常重要,且在各种笔试面试中用到该思想的算法题层出不穷,下图为第一轮的partition算法的一个示例。
事实上,这个地方需要提一下的是,快排有很多种版本。例如,我们“基准数”的选择方法不同就有不同的版本,但重要的是快排的思想,我们熟练掌握一种版本,在最后的笔试面试中也够用了,我这里罗列几种最有名的版本C代码。
1、版本一
我们选取数组的第一个元素作为主元,每一轮都是和第一个元素比较大小,通过交换,分成大于和小于它的前后两部分,再递归处理。代码如下
/**************************************************
函数功能:对数组快速排序
函数参数:指向整型数组arr的首指针arr;
整型变量left和right左右边界的下标
函数返回值:空
/**************************************************/
void QuickSort(int *arr, int left, int right)
{
int i,j;
if(left<right)
{
i=left;j=right;
arr[0]=arr[i]; //准备以本次最左边的元素值为标准进行划分,先保存其值
do
{
while(arr[j]>arr[0] && i<j)
j--; //从右向左找第1个小于标准值的位置j
if(i<j) //找到了,位置为j
{
arr[i] = arr[j];
i++;
} //将第j个元素置于左端并重置i
while(arr[i]<arr[0] && i<j)
i++; //从左向右找第1个大于标准值的位置i
if(i<j) //找到了,位置为i
{
arr[j] = arr[i];
j--;
} //将第i个元素置于右端并重置j
}while(i!=j);
arr[i] = arr[0]; //将标准值放入它的最终位置,本次划分结束
quicksort(arr, left, i-1); //对标准值左半部递归调用本函数
quicksort(arr, i+1, right); //对标准值右半部递归调用本函数
}
}
2、版本二
随机选基准数的快排//使用引用,完成两数交换
void Swap(int& a , int& b)
{
int temp = a;
a = b;
b = temp;
}
//取区间内随机数的函数
int Rand(int low, int high)
{
int size = hgh - low + 1;
return low + rand()%size;
}
//快排的partition算法,这里的基准数是随机选取的
int RandPartition(int* data, int low , int high)
{
swap(data[rand(low,high)], data[low]);//
int key = data[low];
int i = low;
for(int j=low+1; j<=high; j++)
{
if(data[j]<=key)
{
i = i+1;
swap(data[i], data[j]);
}
}
swap(data[i],data[low]);
return i;
}
//递归完成快速排序
void QuickSort(int* data, int low, int high)
{
if(low<high)
{
int k = RandPartition(data,low,high);
QuickSort(data,low,k-1);
QuickSort(data,k+1,high);
}
}
5)考察点,重点和频度分析完全考察快排算法本身的题目,多出现在选择填空,基本是关于时间空间复杂度的讨论,最好最坏的情形交换次数等等。倒是快排的partition算法需要特别注意!频度极高地被使用在各种算法大题中!详见下小节列举的面试小题。
6)笔试面试例题
这里要重点强调的是快排的partition算法,博主当年面试的时候就遇到过数道用该思路的算法题,举几道如下:
例题1
最小的k个数,输入n个整数,找出其中最下的k个数,例如输入4、5、1、6、2、7、3、8、1、2,输出最下的4个数,则输出1、1、2、2。
当然,博主也知道这题可以建大小为k的大顶堆,然后用堆的方法解决。
但是这个题目可也以仿照快速排序,运用partition函数进行求解,不过我们完整的快速排序分割后要递归地对前后两段继续进行分割,而这里我们需要做的是判定分割的位置,然后再确定对前段还是后段进行分割,所以只对单侧分割即可。代码如下:
void GetLeastNumbers_by_partition(int* input, int n, int* output, int k)
{
if(input == NULL || output == NULL || k > n || n <= 0 || k <= 0)
return;
int start = 0;
int end = n - 1;
int index = Partition(input, n, start, end);
while(index != k - 1)
{
if(index > k - 1)
{
end = index - 1;
index = Partition(input, n, start, end);
}
else
{
start = index + 1;
index = Partition(input, n, start, end);
}
}
for(int i = 0; i < k; ++i)
output[i] = input[i];
}