排序算法:
**排序算法的稳定性:**
在待排序的数据中,如果有值相同的数据,在排序的过程中如果不会改变它们的相对顺序,则认为该排序算法为稳定的。
4 2 1 0 9 4 3 8 7
a b
0 1 2 3 4 4 7 8 9
a b【稳定的】
b a【不稳定的】
**冒泡:**
对数据左右进行比较,把最大的交换到最后,特点是该算法对数据的有序性敏感,在排序的过程中发现有序可以立即停止,如果待排序的数据基本有序,则冒泡排序的效率是非常高的。
时间复杂度:平均:O(n^2) 最优:O(n)
稳定性: 稳定的
**选择:**
假定最开始的位置是最小值的下标并记录该下标为min,然后与后面的数据进行比较,如果有比min位置的数据小的,则更新min为更小的数据的下标,最后如果最后min的值发生了变化,则交换min位置的数据与最开始位置的数据,虽然时间复杂度较高,但是数据交换的次数比较少,因此实际运行的速度并不慢
选择排序是冒泡排序的一个变种,但是对数据的有序性不敏感,数据基本有序时冒泡快,数据较为混乱时选择快
时间复杂度:O(n^2)
稳定性:不稳定的 10 10 1
**插入:**
把数据看成两部分,一部分是有序的,剩余的数据逐个插入进去,当数据全部插入完成后,整个数据就是有序的。
适合对已排序好的数据,新增数据并排序
时间复杂度:zO(n^2)
稳定性:稳定的
**快速:**
83 86 77 15 [93] 35 86 92 49 21
83 86 77 15 21 35 86 92 49 93
83 86 77 15 [21] 35 86 92 49
15 21 77 86 83 35 86 92 49 93
77 86 83 [35] 86 92 49
15 21 35 86 83 77 86 92 49 93
86 83 [77] 86 92 49
15 21 35 49 77 86 86 92 83 93
86 [86] 92 83
15 21 35 49 77 86 83 86 92 93
[86] 83
15 21 35 49 77 83 86 86 92 93
找到一个标杆,一方面从左边找比标杆值大的数据,找到后放在标杆的右边,另一方面从右边找比标杆值小的数据,找到后放在标杆的左边,最终标杆左边的数据都比它小,右边的数据都比它大,这样整体有序,然后再按照同样的方式排序标杆左右两边的的数据。
它的综合性能高,因此叫快速排序,笔试考的最多的是快排
时间复杂度:O(nlogn)
稳定性:不稳定
**归并:**
先把一组数据拆分成单独的个体,然后以从小到大的顺序进行合并,由于需要使用额外的内存空间因此避免了数据的交换的耗时,也是一种典型的以空间换时间的算法
时间复杂度 O(nlogn)
稳定性:稳定的
**堆:**
把数据当做完全二叉树,然后在树中调整为大根树,然后把根节点交换到末尾,然后数量--,接下去继续调整为大根树,直到节点数量为1时结束,结束后该数据就是有序的。
时间复杂度 O(nlogn)
稳定性:不稳定的
代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define swap(a,b) {typeof(a) t=a;a=b;b=t;}
#define LEN 10
// 冒泡排序
void bubble_sort(int* arr,size_t len)
{
// 标志位判断是否排序完成
bool flag = true;
for(int i=len-1; i>0 && flag; i--)
{
flag = false;
for(int j=0; j<i; j++)
{
//printf("===========\n");
if(arr[j] > arr[j+1])
{
swap(arr[j],arr[j+1]);
flag = true;
}
}
}
}
// 选择排序
void select_sort(int* arr,size_t len)
{
for(int i=0; i<len-1; i++)
{
int min = i;
for(int j=i+1; j<len; j++)
{
if(arr[j] < arr[min]) min = j;
}
if(min != i) swap(arr[min],arr[i]);
}
}
// 插入排序
void insert_sort(int* arr,size_t len)
{
for(int i=1,j=0; i<len; i++)
{
int val = arr[i];
for(j=i; j>0 && arr[j-1] > val;j--)
{
arr[j] = arr[j-1];
}
if(j != i) arr[j] = val;
}
}
void _quick_sort(int* arr,int left,int right)
{
if(left >= right) return;
// 计算标杆下标位置
int pi = (left+right)/2;
// 备份标杆的值
int pv = arr[pi];
// 备份左右标杆的下标位置
int l = left, r = right;
//当左右标杆相遇结束
while(l < r)
{
// 在标杆左边找比pv大的数据
while(l<pi && arr[l]<=pv) l++;
if(l<pi)
{
//找到后
arr[pi] = arr[l];
pi = l;
}
// 在标杆右边找比pv小的数据
while(r>pi && arr[r]>=pv) r--;
if(r>pi)
{
//找到了
arr[pi] = arr[r];
pi = r;
}
}
// 还原标杆
arr[pi] = pv;
//show_arr(arr,LEN);
//如果左边数据不少2个,左边继续快排
if(pi-left>1) _quick_sort(arr,left,pi-1);
if(right-pi>1)_quick_sort(arr,pi+1,right);
}
// 快速
void quick_sort(int* arr,size_t len)
{
_quick_sort(arr,0,len-1);
}
// 合并
void merge(int* arr,int* tmp,int l,int p,int r)
{
//l是左部分最左 p是左部分最右 p+1是右部分最左 r是右部分最右
//认为合并前 左右部分各自是有序的
if(arr[p] <= arr[p+1]) return;
int i = l, j = p+1, k = l;
while(i<=p && j<=r)
{
// 从左右部分的最左开始,比较谁小谁先放如tmp
if(arr[i] < arr[j])
tmp[k++] = arr[i++];
else
tmp[k++] = arr[j++];
}
// 任意一部分放入结束后,把另一部分剩余的放入tmp末尾
while(i<=p) tmp[k++] = arr[i++];
while(j<=r) tmp[k++] = arr[j++];
// 把tmp合并排序好后的数据重新赋值给arr对应的位置
while(l<=r) arr[l] = tmp[l++];
}
// 拆分
void _merge_sort(int* arr,int* tmp,int l,int r)
{
if(l >= r) return;
int p = (l+r)/2;
_merge_sort(arr,tmp,l,p);
_merge_sort(arr,tmp,p+1,r);
//合并
merge(arr,tmp,l,p,r);
}
// 归并
void merge_sort(int* arr,size_t len)
{
int* tmp = malloc(sizeof(int)*len);
_merge_sort(arr,tmp,0,len-1);
free(tmp);
}
// 构建堆结构
void create_heap(int* arr,int root,size_t len)
{
// root是堆顶的下标,不是编号
if(root >= len) return;
// left right是左右孩子下标
int left = root*2+1, right = root*2+2;
create_heap(arr,left,len);
create_heap(arr,right,len);
if(right<len && arr[right] >arr[left])
swap(arr[right],arr[left]);
if(left<len && arr[left]>arr[root])
swap(arr[left],arr[root]);
}
// 堆排序
void heap_sort(int* arr,size_t len)
{
//调整为大根树
create_heap(arr,0,len);
//交换堆顶与末尾,然后数量--,并剩余重新调整为大根树
for(int i=len-1; i>=0; i--)
{
swap(arr[0],arr[i]);
create_heap(arr,0,i);
}
}