算法稳定性:若待排序表中有两个元素r1和r2,其对应的关键字相同,即key1=key2,且在排序前r1在r2的前面,若使用某一排序算法排序后,r1仍然排在r2前面,则称这个排序算法是稳定的。
排序目的:将无序的数据调整为有序序列
1、冒泡排序——O(n2)_O(1)_稳定
(1)基础实现
void bubble_sort(int *a, int n){
int i, j, t;
for(i = 0; i < n-1; i++){
for(j = 0; j < n-1-i; j++){
if(a[j] > a[j+1]){
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
}
}
}
(2)简单优化
void bubble_sort_adv(int *a, int n){
int i, j, t, flag = 0; //标志未排好序
for(i= 0; i < n-1 && flag == 0; i++){
flag = 1; //假设排好序
for(j = 0; j < n-1-i; j++){
if(a[j] > a[j+1]){
flag = 0;
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
}
}
}
2、选择排序:序列左边都是有序的(相较冒泡排序减少了排序次数)O(n2)_不稳定
void select_sort(int *a, int n){
int i, j, t;
for(i = 0; i < n-1; i++){
for(j = i+1; j < n; j++){
if(a[i] > a[j]){
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
}
3、插入排序
(1)直接插入排序:将无序序列插入有序序列(无序序列中第一个当作有序序列) O(n2)_O(1)_稳定
注:插入排序在什么时候效率高?(1)序列基本有序(2)数据较少
void insert_sort(int *a, int n){
int i, j, t;
for(i = 1; i < n; i++){
if(a[i] < a[i-1]){
t = a[i];
for(j = i-1; j >= 0 && t < a[j]; j--){
a[j+1] = a[j]; //填空位
}
a[j+1] = t;
}
}
}
(2)折半插入排序:在前面有序的序列中,使用折半查找找到待插入元素的正确位置 O(n2)
void insertSort(int a[], int n){
int i, j, low, high, mid;
for(i = 2; i <= n; i++){
a[0] = a[i]; //将a[i]暂存到a[0]
low = 1, high = i-1;
while(low <= high){
mid = (low+high) / 2;
if(a[mid] > a[0]){
high = mid-1;
}else{
low = mid+1;
}
}
for(j = i-1; j >= high+1; --j){
a[j+1] = a[j]; //统一后移元素,空出插入位置
}
a[high+1] = a[0]; //插入操作
}
}
(3)希尔排序(插入排序升级版) 不稳定
注:当数据量很大时,希尔排序较插入排序效率高数倍
思路:先分组,然后对每一组数据分别进行插入排序
先按某一增量对数据进行分组后,对分得的每一组进行插入排序,最后合并为一组数据,再次进行插入排序
增量设置规则:上一轮增量/3+1
void shell_sort(int *a, int n){
int i, j, t, k, increase = n;
do{
increase = increase/3 + 1;
for(i = 0; i < increase; i++){ //增量变化
for(j = i + increase; j < n; j += increase){
if(a[j] < a[j-increase]){
t = a[i];
for(k = j - increase; k >= 0 && t < a[k]; k -= increase){
a[k+increase] = a[k];
}
a[k+increase] = t;
}
}
}
}while(increase > 1);
}
4、快速排序(分治法:大问题分解成小问题,对小问题求解)O(nlogn)不稳定
//注:[left, right]
void quick_sort(int *a, int left, int right){
int i, j, t, temp;
if(left > right){
return;
}
temp = a[left]; //首先找到一个基准数,temp存放的就是基准
i = left;
j = right;
while(i != j){
//顺序重要,要先从右往左找
while(a[j] >= temp && i < j){
j--;
}
//再从左往右走
while(a[i] <= temp && i < j){
i++;
}
if(i < j){
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//基准数归位
a[left] = a[i];
a[i] = temp;
quick_sort(a, left, i-1);
quick_sort(a, i+1, right);
return;
}
5、归并排序:把每次排序结果放在第三方空间,用空间换时间 O(nlogn)_稳定
基本思想:将两个有序的序列合并成一个有序序列
把一个大的序列无限拆分成多个含一个元素的序列,此时序列有序,然后再向上合并直至序列有序
/*--------------------------------***merge_sort***--------------------------------------------
归并排序分为拆、合两个步骤
*/
void merge(int *arr, int start, int mid, int end, int *tmp)
{
int i = start;
int j = mid + 1;
int len = 0;
while (i <= mid && j <= end)
{
if (arr[i] < arr[j])
{
tmp[len] = arr[i];
len++;
i++;
}
else
{
tmp[len] = arr[j];
len++;
j++;
}
}
while (i <= mid)
{
tmp[len] = arr[i];
len++;
i++;
}
while (j <= end)
{
tmp[len] = arr[j];
len++;
j++;
}
//回传到arr[]数组
for (i = 0; i < len; i++)
{
arr[start + i] = tmp[i];
}
}
void merge_sort(int *arr, int start, int end, int *tmp)
{
int mid = (start + end) / 2;
//递归出口
if (start >= end)
{
return;
}
//左半边拆分
merge_sort(arr, start, mid, tmp);
//右半边拆分
merge_sort(arr, mid + 1, end,tmp);
//合并
merge(arr, start, mid, end, tmp);
}
6、堆排序(利用完全二叉树和数组)O(nlogn)_不稳定
区别:每个节点都比它的左右节点大的叫大顶堆,都比它的左右节点小的叫小顶堆
//向下调整堆元素
void downAdjust(int *heap, int i,int num)
{
int t, flag = 0;//flag 记录标记是否需要向下调整
//当有左孩子结点,此时需要调整就执行循环
while (i * 2 <= num && flag == 0)
{
if (heap[i] > heap[i * 2])
{
t = 2 * i; //用t记录值较小的结点
}
else
{
t = i;
}
//有右孩子继续比较
if (i * 2 + 1 <= num)
{
if (heap[t] > heap[2 * i + 1])
{
t = 2 * i + 1;
}
}
//如果发现最小的结点已经不是自己本身
if (t != i)
{
swap(heap, t, i);
i = t; //交换了,把i置为t;
}
else
{
flag = 1;//否则说明不需要调整
}
}
return;
}
//创建堆heap
void create_heap(int *heap, int num)
{
int i;
//从最后一个非叶子节点到第1个结点依次进行调整
for (i = num / 2; i >= 1; i--) //为了便于使用,从序号1开始使用
{
downAdjust(heap, i, num);
}
return;
}
void heap_sort(int *heap, int num)
{
int tmp = num;
create_heap(heap, num);
while (tmp > 1)
{
swap(heap, 1, tmp);
tmp--;
downAdjust(heap, 1, tmp);
}
return;
}