、、
摘要
此前,,,大致讲了一下编程中的一些排序算法。。。
但是这些算法 的实现过程 中 ,,可能会遇到很多特殊的排序场景,,,所以就会出现一些不易察觉的错误 ,,,,下面我就来简要的说说可能遇到的错误 。。。。。
还有就是一些,,,排序算法的优化使用。。。。
选择排序的优化
我们经常说说的
排序
选择的基本思路 ;;;;就是 从 一个数组中个每次找到一个 最大(最小)值,放到适当的位置 ,,,此算法的时间复杂度大致为O(N*N);;;;
但是 我们在面试 的时候 ,,,经常会遇到 这种问题 "让我们写一个选择排序的算法"
一般情况下,,我们写出来的代码是这个样子的
//选择排序
//时间复杂度 为 N*N
void SelectSort(int * array,const size_t n)
{
assert(array);
int end = n;//endl
while(end > 0)
{
end--;
int maxidex = 0 ;
//单趟排序
for(size_t i = 0 ;i <= end;++i)
{
//找到最大的值的下标
if(array[i] > array[maxidex])
{
maxidex = i;
}
}
swap(array[maxidex],array[end]);
}
}
如果要是 写成这样子之后 ,,,面试官肯定是不满意的,,,,,
让我们尝试优化优化,,,,;;;
那么我们就要思考了 ,,要怎么样 优化呢 ???
我们 可以从本质上找解决方法,,,,,,
选择排序的思想是 找一个极端值 ,,,放到适当的位置 ,,,,,
那么,,我们 为什么 不能找到两个极值,,,(一个最大值,,,一个最小值 )
(最小值放到左边,最大值放到右边) 假设是排升序;;;
代码写法
//选择排序的优化
//同时 找的最大的还有最小的
//时间复杂度为 N*N
//求时间复杂度 此时 不要 只看循环的次数 ,,,要看结构
//两个循环都是 都是从两边到中间 N次
void SelectSort(int * array,const size_t n)
{
assert(array);
int left = 0;//left表示的是最左位
int right = n-1;
while(left < right)
{
int maxidex = left ;//先定义一个基值
int minidex = left;
//单趟排序 从 left 走到right
for(size_t i = left;i <=right;++i)
{
if(array[i]> array[maxidex])//得到最大值的下标
{
maxidex = i;
}
if(array[i] < array[minidex] )//得到最小值的下标
{
minidex = i ;
}
}
//此处要注意 的是 ,,,要是 maxidex的值正好就是left
//如果要是交换的话 ,,那么就要将 最大值的位置 更改
swap(array[left],array[minidex]);
swap(array[right],array[maxidex]);
++left;
--right;
}
}
代码写完之后一调试,,没有错误 ,,,得到的结果也是正确的,,但是 这就正确了 吗???。。。
但是要是使用这组数据
{9,5,4,9,3,6,8,7,1,0};
进行调试的话,,,,
最后排序生成的数列就是 这个样子的
为什么会出现这个情况呢????
、
那是因为当第一次进行单趟排序的时候 ,,,
得到的
maxidex = 0 ;
minidex = 9 ;
left = 0;
right = 9;
用图案来看看吧
我们明显可以看出
最大值的下标maxidex 与 和left相等的话,,,,,
那么这段代码就会出现问题 ,,,,(也就是说 maxidex的值已经 被改变了)
所以,我们必须在交换最大值 的时候 判断一下,,,最大的值 是不是 已经改变了。。
//此处要注意 的是 ,,,要是 maxidex的值正好就是left
//如果要是交换的话 ,,那么就要将 最大值的位置 更改
swap(array[left],array[minidex]);
if(maxidex == left)//更改最大的值的位置
{
maxidex = minidex;
}
swap(array[right],array[maxidex]);
++left;
--right;
快速排序 优化。。。
快速排序是面试官很喜欢考的一个问题,,,,因为它看上去不是很好理解。。。
关于,,,,
对于 一个排序 来说 ,,,
要写的就是 先写单趟排序 ,,,,
快速排序的主要思想的 取一个值作为关键值 ,,
每一趟排序,,,,就是 把大于关键值的放到右边,,,,小于关键值的放到左边。。。。
对于这样的
单趟排序
,,,,网上有 提供了三种方法,,,来解决这种方法。。。。
1、、、左右指针交换法
主要思想就是,,,从左边找到大于关键值的 ,,,从右边找到小于关键值的 然后将两个值交换
代码的实现
//1、左右指针交换法
int PartSort(int * array,int left,int right)
{
//取一个中间值作为 关键字
int key = right;
while(left < right)
{
//从左边找到一个比关键值大的
while(left<right&& array[left] <=array[key] )//等号必须加上 否则出现错误(死循环)
{
++left;
}
//从右边找到一个 比 关键值小的
while(left < right && array[right] >= array[key])
{
--right;
}
swap(array[left],array[right]);
}
swap(array[left],array[key]);
return left;//需要返回一个中间值
}
在这里 有一个 ,,,重要的地方 必须提一下;;就是这句代码
while(left<right&& array[left] <=array[key] )//等号必须加上 否则出现错误(死循环)
要是 不加上等号的话
2、、、挖坑法
所谓的挖坑法,,,与左右指针交换法的原理 是 差不多的,,,,就是
先选一个值作为key值,,,, 并且将此位置 当做 一个坑,,,
从 另一端开始 ,,,向前走,,,
假设将 最右端的值 当做 是 key ,,,, ,,,那么将 此位置当做是坑,,,
在从左端,,开始 找到一个比 key大的值 ,,将此位置的值 ,,,放到坑里 ,,,,再将这个位置 当做是 坑 ,,,从 右往前继续判断找比key小的值
代码实现
int PartSort(int * array,int left,int right)
{
int key = array[right];//得到key值
while(left < right)
{
//将right作为坑
//找到比key大的数 填到 坑中
while(left < right && array[left]<= key)
{
left++;
}
array[right] = array[left];
if(left< right)
--right;
while(left <right&& array[right]>= key)
{
--right;
}
array[left] = array[right];
if(left<right)
left++;
// 0 1 2 3 4 5
}
array[left] =key;
return left;
}
3、、、前后指针法
前后指针是一个非常实用的方法,,,
如果要是有一个题 ,,让我们将 一个单链表里的数据 进行排序的话,,那么这里的前两种方法都不能 使用了;;;
在这只能使用 下面这种方法了。。。
所谓前后 指针法就是 ,,,
找一个key值 ,,,
定义两个 变量 cur ,,prev ,,一个指向当前的数据 ,,一个指向的是 前一个数据。。。
假设key值取最右边的值;;;
那么我们 cur 可取下标 0;
prev 取-1;
然后 判断 ,,要是 cur上的值 比 key小的话 ,,,,那么++prev;;;;交换 cur与prev上的值 ,,,
否则 prev 不变 ,,, cur 继续向前走 。。。
代码实现
int PartSort(int *array,int left,int right)
{
//assert(array);
int cur = left;
int prev = left-1;//用来表示 的是 比key大的前一个下标
int key = array[right];
while(cur < right)
{
if(array[cur] <= key)//找到一个比 key小的
{
prev++;//找到比 key大的
swap(array[cur],array[prev]);//交换 把 大的 放到后面
}
cur++;//cur向后走
}
++prev;
swap(array[right],array[prev]);
return prev;
}
总体代码的实现
void QuickSort(int * array,int left,int right)
{
assert(array);
//如果要是
if(right-left <20)
{
InsertSort(array+left,right-left+1);
return ;
}
int ret = PartSort2(array,left,right);//得到分割点
//将左边的进行 排序
QuickSort1(array,left,ret-1);
//将右边的进行 排序
QuickSort1(array,ret+1,right);
}
对于快速排序的优化
快速排序的时间复杂度,,,大致 可以看成是 O(N*logN)
但是 ,,当每次选的key值 是 一个极值的话,,,,那么,,算法时间复杂度就是 最坏的情况。。O(N*N)
对于此种场景我们提供了一种方法,,,就是 ,,
三数取中法
就是每次的单趟排序时,,都要,将左右端的值 与中间的值比较找到中间值,,,当做是 key,,,
可以明显的增加速率
还有的就是 如果当单趟排序元素的个数小的时候,,,使用快速排序法,,,,,
所以当排序元素小的时候我们可以使用插入排序,,,,,来减少时间复杂度。。。。
我们称之为
小区间优化。。。
冒泡排序 优化
我们都知道冒泡排序很是很简单,,,但是这类算法可以
优化
要是 ,,数组元素依然有序,,,那么
就不需要交换了 。。 。。
代码实现
void BubbleSort(int *array,const size_t n )
{
assert(array);
for(size_t i = 0 ;i < n-1;++i)
{
//单趟排序
//优化 使用一个数 来记录交换的次数
int j = 0;
int count = 0 ;
for(;j < n-1-i;++j)
{
if(array[j] > array[j+1])
{
swap(array[j],array[j+1]);
count++;
}
}
if(count == 0)//要是交换的次数 为0 表示 已经 是有序的了
break;
}
}