排序作为最基础的算法,有选择排序,冒泡排序,插入排序,希尔排序,堆排序,快速排序,归并排续等,我会用c++写一下,欢迎交流~
排序算法的稳定性
如果在序列A中,Ai = Aj, 且i<j,经过排序之后,如果Ai和Aj的相对位置没有发生改变,则称这个算法是稳定的
比如在下表成绩排名中,老二和老四的成绩排名在排序前和排序后的相对位置没有改变,所以是这个排序是稳定的,如果改变了则是不稳定的
编号-序前 | 姓名 | 成绩 | 编号-序后 | 姓名 | 成绩 | |
---|---|---|---|---|---|---|
1 | 老大 | 135 | 1 | 老五 | 145 | |
2 | 老二 | 140 | 2 | 老二 | 140 | |
3 | 老三 | 130 | 3 | 老四 | 140 | |
4 | 老四 | 140 | 4 | 老大 | 135 | |
5 | 老五 | 145 | 5 | 老三 | 130 |
性能参数
时间性能
辅助空间
算法复杂性
选择排序
选择排序,是在每次循环中,找到一个最小值放在最左侧,时间复杂度为O(n^2)
void selectSort(int *a, int size)
{
for (int i = 0; i < size-1; ++i) {
//j每次循环一次,就选出剩余部分中最小的一个到次左边
int n = i;
for (int j = i+1; j < size; ++j) {
if (a[n] > a[j])
n = j;
}
//相对冒泡排序来说,交换次数较小
if (n != i)
std::swap(a[n], a[i]);
}
}
冒泡排序
冒泡排序,是在每次循环中,将相邻的两个数比较大小,如果反序则交换位置,直到没有反序的位置为止,时间复杂度为O(n^2)
void bubbleSort(int *a, int size)
{
for (int i = 0; i < size-1; i++) {
//j每次循环一次,就交换相邻的两个字符
int isTerminal = 0;
for (int j = 0; j < size-1; j++) {
if (a[j] > a[j+1]){
std::swap(a[j], a[j+1]);
isTerminal++;
}
}
//在i<size-1时,如果序列两两比较时已经是有序的,没有发生过交换,则无需对i后续的遍历
if (!isTerminal)
break;
}
}
插入排序
插入排序是对主循环中a[i]和a[j]比较,如果a[i]<a[j],则交换,然后对数组之前的j个元素比较大小两两交换
void insertSort(int *a, int size)
{
for (int i = 1; i < size; i++) {
for (int j = i-1; j>=0; j--) {
if (a[j+1] < a[j])
std::swap(a[j+1], a[j]);
else
break; //j-1之前已经是有序的了,所以这时候直接break就可以了,能够节省比较次数
}
}
}
希尔排序
希尔排序是第一个时间复杂度为 O(n*log(n)) 的排序算法,主体思路是对相隔gap步长的元素比较大小,无序则进行排序。同插入排序有些相似,只是插入排序的步长为1,而希尔排序的步长是递减为1的。
void shellSort(int *a, int size)
{
int gap = size;
do{
gap = gap/3 + 1;
for (int i = gap; i < size; ++i) {
for (int j = i-gap; j >=0 ; j-=gap) {
if (a[j+gap] < a[j])
std::swap(a[j+gap], a[j]);
}
}
}while (gap>1);
}
堆排序
(大顶堆)
堆排序就是将序列构造成完全二叉树,然后对每个根节点和他的叶子节点,满足根节点元素同时大于左右叶子节点,这样堆顶就是最大值
class heapSort{
public:
heapSort(int *a, int size)
{
//构建大顶堆
for (int i = size/2-1; i >= 0; i--) {
heapJust(a, i, size-1);
}
//不断的将堆顶的值和末尾交换,并且重新构建大顶堆
for (int i = size-1; i > 0; i--) {
std::swap(a[0], a[i]);
heapJust(a, 0, i-1);
}
}
void heapJust(int *a, int s, int n) //s是树的第s层
{
int top = a[s];
for (int i = s*2+1; i <= n; ) {
if ((i < n) && (a[i] < a[i+1])) //(i<n)是因为当i=n的时候,没有i++了
i++;
if (top < a[i]){
a[s] = a[i]; //最大值始终给a[s]
s = i; //更新当前层的索引
i = 2*i+1; //
}else
break;
}
a[s] = top;
}
};
快速排序
快速排序是目前最快的排序算法,整体思路是对一个序列选取一个基准值,通过比较大小将序列分为两个子序列,然后对每个子序列递归做同样的计算,最后得到有序序列.
class quickSort
{
public:
quickSort(int* a, int size){
qsort(a, 0, size-1);
}
//通过Partition函数,找到序列的一个索引,将序列由一分二,然后对两个子序列分别递归调用
void qsort(int* a, int low, int high)
{
int point;
if (low < high){
point = Partition(a, low, high);
qsort(a, low, point-1);
qsort(a, point+1, high);
}
}
//选择一个基准值,通常是序列第一个元素,对该序列从右至左选取第一个小于基准值的元素 right, 将该基准值与 right 交换位置.
// 然后对该序列从左至右选取第一个大于基准值的元素left,将该元素值left与序列最右元素交换.
int Partition(int* a, int low, int high)
{
int point;
point = a[low]; //这里选择序列第一个元素作为基准值
while (low < high){
while (low < high && a[high] >= point){ //>= 而不是>
high--;
}
std::swap(a[low], a[high]);
while (low < high && a[low] <= point){ //<= 而不是<
low++;
}
std::swap(a[low], a[high]);
}
return low;
}
};
归并排续
感觉归并排续和希尔排序有些相似.希尔排序是先对序列的大块排序,后对小块排序.归并排续是对大序列的每个小块先排序最后对大块排序.
归并排序示意图
class mergeSort{
public:
mergeSort(int* a, int size){
if (size > 1){
int *list1 = a;
int size1 = size / 2;
int *list2 = a + size / 2;
int size2 = size - size1;
mergeSort(list1, size1);
mergeSort(list2, size2);
merging(list1, size1, list2, size2, size);
}
}
void merging(int *list1, int size1, int* list2, int size2, const int K_MAX_SIZE)
{
int i,j,k;
i=j=k=0;
int temp[K_MAX_SIZE];
while (i < size1 && j < size2){
if (list1[i] < list2[j]){
temp[k++] = list1[i++];
} else{
temp[k++] = list2[j++];
}
}
while (i < size1){
temp[k++] = list1[i++];
}
while (j < size2){
temp[k++] = list2[j++];
}
for (int m = 0; m < (size1+size2); ++m) {
list1[m] = temp[m];
}
}
};
参考资料
- 牛客网视频
- 数据结构和算法(小甲鱼)–B站