本文总结了十个常见排序。主要是我在找实习复习时学习一像素博主的文章
https://www.cnblogs.com/onepixel/articles/7674659.html
然后自己写了代码,再加上了一些自己的理解所得。
冒泡排序
int sort(int *a, int size)
{
for (int i = size-1; i >= 0; i--)
{
for (int j = 0; j < i; j++)
{
if (a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
return 0;
}
时间复杂度(n-1)+(n-2)+(n-3)+…+1=n*(n-1)/2 即O(n^2).
最好情况下第一次扫描发现没有不符合的项。O(n)
即可将上述代码添加一些条件,第二个for循环,若是一遍下来没发现交换项,即结束。
空间复杂度,交换时占一个,所以O(1).
选择排序
int sort(int *a, int size)
{
for (int i = 0; i < size; i++)
{
int min = a[i], count = i;
for (int j = i; j < size; j++)
{
if (min > a[j])
{
min = a[j];
count = j;
}
}
int temp = a[i];
a[i] = a[count];
a[count] = temp;
}
return 0;
}
时间复杂度同冒泡
最好也要O(n^2) 因为每轮都要找最大,所以是否排好序对程序没有影响,依然要执行很多步。
空间复杂度,比较占用O(1).
插入排序
int sort(int *a, int size)
{
for (int i = 0; i < size; i++)
{
int tem = a[i];
for (int j = i; j > 0; j--)
{
if (a[ j - 1 ] >= tem)
a[ j ] = a[ j - 1 ];
else
{
a[j] = tem;
break;
}
}
}
return 0;
}
每次选择一个元素放在他对应的位置,最快O(n),平均是和冒泡和选择时间一样。
空间复杂度是O(1),需要一个额外的变量存放当前的数。
希尔排序
int sort(int *a, int size)
{
int preIndex = 0, current = 0,adde;
for (adde = size / 2; adde >= 1; adde /= 2)
{
for (int i = adde; i < size; i++)
{
int j = i;
current = a[j];
while (j - adde >= 0 && a[j - adde] > current)
{
a[j] = a[j - adde];
j = j - adde;
}
a[j] = current;
}
}
return 0;
}
设待排序元素序列有n个元素,首先取一个整数作为间隔(上面取得是size一半),将全部元素分为所取整数个子序列,所有距离为该整数的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔(可以逐渐减小,也可以直接/2),重复上述子序列划分和排序工作。直到最后取1,将所有元素放在同一个子序列中排序为止。
由于开始时,整数的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。
时间复杂度和空间复杂度与插入排序一样,但平均时间复杂度,平均性能好很多。
归并排序
void merge_sort(int *data, int start, int end, int *result)
{
if(1 == end - start)//如果区间中只有两个元素,则对这两个元素进行排序
{
if(data[start] > data[end])
{
int temp = data[start];
data[start] = data[end];
data[end] = temp;
}
return;
}
else if(0 == end - start)//如果只有一个元素,则不用排序
return;
else
{
//继续划分子区间,分别对左右子区间进行排序
merge_sort(data,start,(end-start+1)/2+start,result);
merge_sort(data,(end-start+1)/2+start+1,end,result);
//开始归并已经排好序的start到end之间的数据
merge(data,start,end,result);
//把排序后的区间数据复制到原始数据中去
for(int i = start;i <= end;++i)
data[i] = result[i];
}
}
void merge(int *data,int start,int end,int *result)
{
int left_length = (end - start + 1) / 2 + 1;//左部分区间的数据元素的个数
int left_index = start;
int right_index = start + left_length;
int result_index = start;
while(left_index < start + left_length && right_index < end+1)
{
//对分别已经排好序的左区间和右区间进行合并
if(data[left_index] <= data[right_index])
result[result_index++] = data[left_index++];
else
result[result_index++] = data[right_index++];
}
while(left_index < start + left_length)
result[result_index++] = data[left_index++];
while(right_index < end+1)
result[result_index++] = data[right_index++];
}
分为划分和归并过程。
时间复杂度O(nlogn)但需要额外的空间,空间复杂度为O(n)
快速排序
int quickSort(int *a,int start,int end)
{
if (start >= end)
return 0;
int k = start;
int i = start + 1, j = end;
while (i < j )
{
while (a[i] < a[k] && i<end)
i++;
while (a[j] > a[k] && j>start)
j--;
if (i < j)
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
if (a[j]<a[k])
{
int temp = a[k];
a[k] = a[j];
a[j] = temp;
}
quickSort(a, start, j-1);
quickSort(a, j + 1, end);
return 0;
}
也是分区排序的思想,上面程序利用递归来实现,还有非递归,有时间看看。
时间复杂度O(nlogn),每次划分为n/2时情况最好。
空间复杂度传参两个,平均递归深度logn,所以平均O(logn),可以取首中尾三个数据优化。但也不能完全消除退化成冒泡那样的可能。
堆排序
int adjust(int *a,int size)
{
if (size <= 1)
return 0;
int subroot = size / 2 - 1, subright = size / 2 * 2, subleft = size / 2 * 2 - 1;
int largest = subroot;
if (subright <= size - 1 && a[subright] > a[largest])
largest = subright;
if (subleft <= size - 1 && a[subleft] > a[largest])
largest = subleft;
if (largest != subroot)
{
swap(a[largest], a[subroot]);
adjust(a, subroot + 1);
}
else
adjust(a, size - 1);
return 0;
}
int heapSort(int *a,int size)
{
adjust(a,size);
int last = size - 1;
for (int i = 0; i < size; i++)
{
swap(a[0], a[last]);
last--;
adjust(a, last + 1);
}
return 0;
}
采用的是最大堆最小堆的特殊性,此处用数组来表示。
堆排序可以分成两个过程,一个是建最大堆,然后不断取出最大数,再进行堆调整。
建最大堆,即使是最慢的两两比较,又多次重复也还是常数级,然后成了最大堆之后,将堆顶元素向下调整,而由于已经是最大堆了,所以第二层两个有一个一定为剩余中最大,和那个交换,然后将小元素依次向下调整即可。n个元素,logn层,总共取n次最大值。所以复杂度为最大不过n+nlogn。且没有什么最坏或最好情况,因为一旦建成最大最小堆,则不存在好坏。好坏只影响第一次建堆。
我上面的代码可能复杂度比我说的要高,因为我每一次都重复了最麻烦的第一次建堆。
计数排序
int max(int *a,int size)
{
int maxvalue = 0;
for (int i = 0; i < size; i++)
{
if (maxvalue < a[i])
maxvalue = a[i];
}
return maxvalue;
}
int countSort(int *a,int maxValue,int size)
{
int *result = new int[maxValue];
for (int i = 0; i < maxValue; i++)
{
result[i] = 0;
}
for (int i = 0; i < size; i++)
{
result[a[i]-1]++;
}
int k = 0;
for (int i = 0; i < maxValue; i++)
{
for (int j = 0; j < result[i]; i++)
{
a[k] = i+1;
k++;
}
}
delete[] result;
return 0;
}
比较容易理解,就是用一个包括数字大小范围的数组,而且只能是整数,或者小数位数小也可以,如果数字错综复杂而且个数没多少,这样做就不必了。如果数字只是1到1000,却有几十万的数据,那用计数排序确实是会很快。
首先遍历一遍数组,找出最大数。动态建立一个数组,赋初值为0,再遍历一遍原数组。将原数组中含有得数在动态数组中统计数量。最后根据是否有数将之还给原数组。最后记得回收动态建立的数组,将内存还给自由存储区。
时间复杂度2(maxvalue+n) 空间复杂度maxvalue+n
桶排序
代码就不写了,和计数排序差不多,关键就在于桶的划分,划分得越多,排序时间越快,空间消耗越大。
时间复杂度N+K,但最坏情况下取决于桶内用什么排序。
基数排序
也是和上面两个类似。不过是根据位数来划分,本质都是分配、收集。