常见的低级排序算法主要有冒泡排序、选择排序、插入排序;
常见的高级排序算法有快速排序、归并排序、希尔排序、堆排序等
下面注意介绍
1.冒泡排序
首先展示一下在c++中算法的实现过程:
//冒泡排序
for(int i = 0 ; i < len ; i++)
{
for(int j = 0 ; j < len-i-1 ; j++)
{
if(a[j] > a[j+1])
{
//如果前者比后者大那么交换两者,这里默认按照升序排序
int temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
}
冒泡排序共有两层循环,最里面的循环是为了将外层循环中的所有数字中的最大值放在最外层循环的末尾。经历len-1次外层循环则退出。
这种方法最坏、最好情况下的时间复杂度都为o(n2)。要原因是内层循环不管是不是进行了交换都要循环len-1次。如果是在最好的情况下例如:
a[10] = {1,2,3,4,5,6,7,8,9,10};
那么在第一次进行内层的循环的时候就应该知道所有的元素都已就位(数组按照由大到小的元素排列好),这个时候应该直接跳出最外层循环,所以根据这种方法可以将上述冒泡排序算法进行改进。
//冒泡排序优化
bool exchange = true;
for(int i = 0 ; i < len ; i++)
{
if(exchange == false)
{
break;
}
for(int j = 0 ; j < len-i-1 ; j++)
{
if(a[j] > a[j+1])
{
//如果前者比后者大那么交换两者,这里默认按照升序排序
int temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
exchange = true;
}
exchange = true;
}
}
这里面新增加一个bool值exchange用来判断内层循环是否交换过值,将exchange = false,这样在呢曾循环完毕时自动判断exchange,为false直接跳出循环。如果交换过exchange = true说明还处于无序状态则继续。
特别需要注意的是这种改进对于平均时间复杂度并没有改善依然为o(n2),只是对于某些极端情况下时间复杂度大大降低。
2.选择排序
先上代码
// for(int i = 0 ; i < len ; i++)
// {
// int Min = i;//将i默认作为最小值的下标
// for(int j = i+1 ; j<len ; j++)
// {
// if(a[Min] > a[j])//把这里的大于号改成小于号就是降序
// Min = j;//这个时候a[j]更小,我们要更新min
// }
// if(Min != i)//内存循环执行完毕这时min是[i,len)中最小元素的小标,但是min不一定等于i。
// {
// int temp = a[Min];//这里将[i,len)真正地最小值赋值给a[i]
// a[Min] = a[i];
// a[i] = temp;
// }
// }
选择排序的最好、最坏情况下的时间复杂度都为o(n2)。无法改进。
3.插入排序
这种排序算法我们都很熟悉了,如果你打牌的时候喜欢将新抓到的牌按照从小到大或者从大到小的顺序排列,那其实你在就是在做插入排序。
假设我们现在把x看作是我们新抓到的牌,那么将这个牌插到哪里最合适呢?我们假设你手头的牌都是已经从小到的排序好的,我们先将拿到的牌放在数组末尾,那么现在的牌面为(a,b,c,d.....w,x)。那么
- 你要先和最后一张牌x比较,z大于x不用动,如果z小于x那么我们还要接着往前找直到找到一个不大于(<=)z的牌然后将z插到这张牌的前面。这里的<=需要注意,因为这将影响到这个算法是不是稳定的,我们稍后再详细讨论。
- 对于每个新来的牌都重复(1)的操作。直到没有新的牌。
这里面注意之前手头可以没有牌。那就直接把Z插入排头就行。
不多逼逼直接上程序
int i= 0;//这是我们第一次捉到的牌,i代表的是手中牌的数目减一
while(i < len)
{
int j = i+1;//这是我们第二次捉到的牌
int temp = a[j];//这一步是将我们新拿到的牌做个备份原因后面再说
while(--j >-1 && temp < a[j] )//如果j还没有脱离[0,j]区间下限且手中的牌要比上一张小
{
a[j+1] = a[j];//这相当于把小与等于temp的往前挪一位,这个时候a[j]可以看为空的
}
a[j+1] = temp;//退出循环的时候j+1便是新拿到的牌应该插入的位置
i++;//我们要接着抓牌
}
这里面有几处比较难理解
- 首先我们要保证我们新拿到的牌不会丢,因此我们用了一个temp来接.
- While循环的退出时机是--j=-1或temp>=a[j]。这里其实是一个短路求值,先判断--j = -1便直接退出.大家可以用两个数实验.
或者用下面的代码能更好的解释插入排序的思想。
/插入排序2.0
//外层的无序序列
for (int j =1 ; j < len ; j++)
{
int key = a[j];
int index = j;
//有序序列
for(int i = j-1; i >= 0 ; i--)
{
cout << i << "\t";
if(key < a[i])
{
//后已操作
a[i+1] = a[i];
index = i;//更新坑的位置
}
else///key>=a[i] 那么现在的index就是坑
{
break;
}
} cout << endl;
//填坑
a[index] = key;
}
4.快速排序
快速排序的本质就是让一个数的左边都是小于他的数,右边都是大于等于他的数。
先上代码
因为要递归调用所以先写一个函数
quickSort(int a[] , int lo ,int hi)
{
///把a[lo]先复制一份,为了找a[lo]的位置
int temp = a[lo];
int i = lo;
int j = hi;
while(i < j)
{
///在左边找第一个小于等于temp的数
while( i < j && a[j] > temp)
j--;
///将a[i]作为坑
if(i<j)
{
a[i] = a[j];
}
///在左边找第一个大于temp的数
while(i < j && a[i] <= temp)
i++;
///将a[j]作为坑
if(i<j)
{
a[j] = a[i];
}
}
//退出循环的i=j时候找到了temp插入的位置,这时i=j左边都小于等于temp,右边大于temp
a[i] = temp;
//递归调用
quickSort(a,lo,i-1);
qinckSort(a,i+1,hi);
}
首先看有三层循环(while)
第一层循环是为了保证最后的i和j相等时候,a[i] = temp使得a[i]左边都是小于他的数,右边都是大于等于他的数。
第二层循环+if判断语句是为了找到右侧部分小于temp的值并转移到a[i]中。
第三层循环+if判断语句是为了找到左侧部分大于等于temp的值并转移到a[i]中。
(ps从这里也能看出来这种算法是不稳定的)
注:快速排序中的主元也就是temp的取值不一定要从最左边选取。
5.希尔排序
希尔排序与其他的排序算法最重要的不同就是他每次都将数组分成若干份,然后对着若干份的数组进行插入排序。每次分组后的插入排序都会使得数组变的更为有效,也就是的下一次分组、插入排序的效率越来越高。本质上来说希尔排序是对插入排序的一种改进。每次选取的增量步决定了希尔排序的效率。我们假设数组长度为n,那么增量步选择为n......n/2....1。
代码如下:
void shellSort( int a[], int len)
{
//提高代码的健壮性
if(len<2)
return;
int increase = len;
while(increase > 1)
{
//选择增量步
increase = increase/3 +1;
//插入排序,当increase=1是就是插入排序只不过此时的数组有序得多
//首先分组,一共有increase个组
for(int i = 0 ; i < increase ; i++)
{
//插入排序
//外层的无序序列
for (int j = i+increase ; j < len ; j+=increase)
{
int key = a[j];
int index = j;
//有序序列
for(int k = j -increase ; k >= 0 ; k-=increase)
{
if(key < a[k])
{
//后已操作
a[k + increase] = a[k];
index = k;//坑的位置
}
}
//填坑
a[index] = key;
}
}
}
}
6.归并排序
///归并排序
void Merge(int arr[], int low,int mid, int high)
{
int i = low;
int j = mid+1;
int k = 0;
int* temp = new(nothrow) int[high-low+1];
if(!temp )
{
cout << "error" << endl;
return;
}
///这个循环完了i或j一定有一个越界
while(i <= mid && j <= high)
{
if(arr[i]<=arr[j]) //较小的先存入temp中
temp[k++]=arr[i++];
else
temp[k++]=arr[j++];
}
///j越界
while(i <= mid)
temp[k++] = arr[i++] ;
///i越界
while(j <= high)
temp[k++] = arr[j++] ;
///将temp中的数组导入a中
for(i=low,k=0;i<=high;i++,k++)//将排好序的存回arr中low到high这区间
arr[i]=temp[k];
///释放内存空间
delete[] temp;
}
void mergeSort(int arr[], int low, int high)
{
if(low < high)
{
int mid = (high+low)/2;
mergeSort(arr,low,mid);
mergeSort(arr,mid+1,high);
Merge(arr,low,mid,high);
}
}