一、冒泡排序
时间复杂度:平均情况:O(n^2) 最好情况:O(n)
空间复杂度:O(1)
稳定性:稳定
主要思路:
1.比较相邻的元素。如果第一个比第二个大,就交换它们两个。
2.对每一个相邻元素做同样的工作,从开始第一对到结尾的每一对。在这一 点,最后的元素应该会是最大的数。
3.针对多有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
图解:
代码:
void Bubble_sort(int *arr, int len)
{
assert(arr != NULL);
int tmp = 0;
bool swap = false;//加判断条件 减少比较次数
for (int i = 0; i < len -1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;//交换
swap = true;
}
}
if (!swap)
{
return;
}
}
}
void Show(int *arr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int drr[] = {1,8,7,5,0};
int num = sizeof(drr) / sizeof(drr[0]);
Bubble_sort(drr, num);
Show(drr, num);
return 0;
}
二、选择排序
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定(相同的数值都交换了)
主要思路:
每一次从无序组的数据元素中选出最小的一个元素,存放在无序组的起始位置,无需组的元素减少,有序组的元素增加,直到全部待排序的数据元素排完。
图解:
代码:
void select_sort(int *arr, int len)
{
assert(arr != NULL);
int i, j;
int tmp;
int min = 0;
for (i = 0; i < len; i++)
{
int min = i;
for (j = i + 1; j < len; j++)
{
if (arr[j] < arr[min])
{
tmp = arr[min];
arr[min] = arr[j];
arr[j] = tmp;
}
}
}
}
void Show(int *arr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int drr[] = { 1,8,7,5,0 };
int num = sizeof(drr) / sizeof(drr[0]);
select_sort(drr, num);
Show(drr, num);
return 0;
}
三、插入排序
直接插入排序:
时间复杂度:无序 O(n^2) 有序O(n)
空间复杂度:O(1)
稳定性:稳定
主要思路:
插入排序是最简单常用的方法,将数组分为两部分,排好序的数列,以及未排序的数列,将未排序的数列中的元素 与排好序的数列进行比较,然后将该元素插入到已排序列的合适位置中。
图解:
代码:
void Insert_sort(int *arr, int len)
{
assert(arr != NULL);
int i, j;
int tmp = 0;
for (i = 1; i < len; i++)
{
tmp = arr[i];
for (j = i - 1; j >= 0; j--)
{
if (arr[j] > tmp)
{
arr[j + 1] = arr[j];
}
else
{
break;
}
}
arr[j + 1] = tmp;
}
}
四、希尔排序:(直接插入的优化)
时间复杂度:O(n^1.5-n^1.5)
空间复杂度:O(1)
稳定性:不稳定
工作原理:
把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
图解:
代码:
void Shell(int *arr, int len,int gap)
{
assert(arr != NULL);
int tmp = 0;
int i, j;
for (i = gap; i < len; i += gap)
{
tmp = arr[i];
for (j = i - gap; j >= 0; j -= gap)
{
if (arr[j] > tmp)
{
arr[j + gap] = arr[j];
}
else
{
break;
}
}
arr[j + gap] = tmp;
}
}
void Shell_sort(int *arr, int len)
{
int drr[] = { 5,3,1 };//分组 必须是素数 最后一个必须为1
int lend = sizeof(drr) / sizeof(drr[0]);
for (int i = 0; i < lend; i++)
{
Shell(arr, len, drr[i]);
}
}
五、堆排序
时间复杂度:O(nlog2n)
空间复杂度:O(1)
稳定性:不稳定
主要思路:
1.将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。
2.将其与末尾元素进行交换,此时末尾就是最大值。
3.然后将剩余n -1 个元素重新构造成一个堆,这样会得到n个元素的次小值。
4.如此反复执行,便得到一个有序序列。
图解:
代码:
void Adjust(int *arr,int start,int end)
{
assert(arr != NULL);
int tmp = arr[start];
for(int i = 2*start + 1;i <= end;i = 2*i+1)
{
if(i < end && arr[i] < arr[i + 1])//是否有右孩子
{
i++;//最大值的下标
}
if(arr[i] > tmp)
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = tmp;
}
void HeapSort(int *arr,int len)
{
for(int i = (len - 1 - 1)/2;i >= 0;i--)
{
Adjust(arr,i,len - 1);
}
for(int i = 0;i <= len - 1;i++)
{
int tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
Adjust(arr,0,len - 1 - 1 - i);
}
}
六、快速排序
时间复杂度:最好情况下:O(nlong2n) 最坏情况下:O(n^2)
空间复杂度:O(nlong2n)
稳定性:不稳定
主要思路:
快速排序是找出一个元素(理论上可以随便找一个)作为基准,然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的值都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n - 1 个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正确位置,排序完成。所以快速排序算法的核心算法是分区操作,及如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
图解:
代码:
//快排的递归
int Partition(int *arr,int left,int right)
{
int tmp=arr[left];//将第一个给tmp
while(left1
{
while(lefttmp)//右边的大于tmp时
{
right--;
}
if(left>=right)//第一次循环完了
{
break;
}
else
{
arr[left]=arr[right];
}
while(leftif(left>=right)
{
break;
}
else
{
arr[right]=arr[left];
}
}
arr[left]=tmp;
return left;//返回相遇的那个位置
}
void Quick(int *arr,int start,int end)
{
int par=Partition(arr,start,end);
if(par>=start+1)
{
Quick(arr,start,par-1);//当前面部分有两个以上的数时再次排序
}
if(par1)
{
Quick(arr,par+1,end);//当后面部分有两个以上的数时再次排序
}
}
void Quick_Sort(int *arr,int len)
{
Quick(arr,0,len-1);//调用Quick函数 排序
}
非递归
void Quick_Sort1(int *arr,int len)
{
int *stack=(int *)malloc(sizeof(int)*len);//定义栈的大小
assert(stack!=NULL);//断言
int top=0;//指向栈底
int left=0;//最左端
int right=left-1;//指向最右端
int par=Partition(arr,left,right);
if(par>left+1)//左边有数时 入栈
{
stack[top++]=left;
stack[top++]=par-1;
}
if(par1)//右边有数时
{
stack[top++]=par+1;
stack[top++]=right;
}
while(top>0)//出栈
{
right=stack[--top];
left=stack[--top];
par=Partition(arr,left,right);
if(par>left+1)
{
stack[top++]=left;
stack[top++]=par-1;
}
if(par1)
{
stack[top++]=par+1;
stack[top++]=right;
}
}
free(stack);
stack=NULL;
}
快排的三种优化:
1.随机选取基准法
void Swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
void Quick(int *arr,int start,int end)
{
Swap(arr,start, rand()%(end-start) + start);//每次需要在high这面产生随机值
int par = Partion(arr,start,end);
if(par > start+1)//左边是不是有两个数据以上
{
Quick(arr,start,par-1);
}
if(par < end-1)
{
Quick(arr,par+1,end);
}
}
2.三分取中法
/*void Swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
void Median_of_Three(int *arr,int start,int mid,int end)
{// arr[mid]<=arr[start]<=arrr[end]
if(arr[mid] > arr[end])//arr[mid]<=arr[end]
{
Swap(arr,mid,end);
}
if(arr[start] < arr[mid])//arr[mid]<=arr[start]
{
Swap(arr,start,mid);
}
if(arr[start] > arr[end])
{
Swap(arr,start,end);
}
}
void Quick(int *arr,int start,int end)
{
Median_of_Three(arr,start,(end-start)/2+start,end);
int par = Partion(arr,start,end);
if(par > start+1)//左边是不是有两个数据以上
{
Quick(arr,start,par-1);
}
if(par < end-1)
{
Quick(arr,par+1,end);
}
}
3.聚集相同元素法
void Swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
void Focus_Same_Num(int *arr, int low, int par, int high, int *left, int *right) //优化4
{
int parLeft_Index = par-1;
int parRight_Index = par+1;
for(int i = par-1;i >= low;i--)
{
if(arr[i] == arr[par] && i != parLeft_Index)
{
Swap(arr,i,parLeft_Index);
parLeft_Index--;
}
}
*left = parLeft_Index;
for(int i = par+1;i <= high;i++)
{
if(arr[i] == arr[par] && i != parRight_Index)
{
Swap(arr,i,parRight_Index);
parRight_Index++;
}
}
*right = parRight_Index;
}
void Quick(int *arr,int start,int end)
{
int par = Partion(arr,start,end);
int par_left = par-1;
int par_right = par+1;
Focus_Same_Num(arr,start,par,end,&par_left,&par_right);
if(par > start+1)//左边是不是有两个数据以上
{
Quick(arr,start,par-1);
}
if(par < end-1)
{
Quick(arr,par+1,end);
}
}
七、基数排序
时间复杂度:最好情况:O(d(n+rd)) 最坏情况:O(d(r+n)) r代表关键字的基数 d代表长度 n代表关键字的个数
空间复杂度:O(rd+n)
稳定性:稳定
主要思路:
将整数按位数切割成不同的数字,然后按每个位数分别比较。
将所有待比较数值统一为同样的数位长度,数位较短的数前面补0.然后,从最低位开始,依次进行排序。这样从最低位一直到最高位排完序后,数列就变成一个有序序列。
图解:
第一次入桶:按个位数大小
第二次入桶:按十位数大小
第三次入桶: 按百位数的大小
代码:
typedef struct Node
{
int data;
struct Node *next;
}Node, *List;
//运用单链表
void InitList(List plist)//初始化
{
assert(plist != NULL);
if (plist == NULL)
{
return;
}
plist->next = NULL;
}
static Node *GetNode(int val)
{
Node *p = (Node *)malloc(sizeof(Node));
assert(p != NULL);
p->data = val;
p->next = NULL;
return p;
}
bool Insert_tail(List plist, int val)//尾插
{
Node *p;
for (p = plist; p->next != NULL; p = p->next)
{
;
}
Node *pGet = GetNode(val);
p->next = pGet;
return true;
}
bool DeletFirst(List plist, int *rtv)//去掉第一个
{
assert(plist != NULL);
Node *p=plist->next;
if (plist->next == NULL)
{
return false;
}
if (rtv != NULL)
{
*rtv = p->data;
}
plist->next = p->next;
free(p);
p = NULL;
return true;
}
int GetMaxBit(int *arr, int len)//获取最大数的位数
{
int Max =arr[0];
for (int i = 0; i <= len - 1; i++)
{
if (arr[i] > Max)
{
Max = arr[i];
}
}
int count = 0;
while (Max != 0)
{
count++;
Max /= 10;
}
return count;
}
int GetNum(int num, int date)//得到num的第date位数字
{
for (int i = 0; i < date; i++)
{
num /= 10;
}
return num % 10;
}
void Radix(int *arr, int len, int date)
{
Node head[10];
for (int i = 0; i < 10; i++)
{
InitList(&head[i]);
}
int i;
int tmp;
for(i = 0; i < len; i++)//入桶
{
tmp = GetNum(arr[i],date);
Insert_tail(&head[tmp],arr[i]);
}
i = 0;
for(int j = 0; j < 10;)//出桶
{
if(DeletFirst(&head[j], &arr[i]))
{
i++;//出一桶内的数据
}
else
{
j++;//一桶内数据出完后
}
}
}
void RedixSort(int *arr, int len)
{
int count = GetMaxBit(arr, len);//进出桶多少次
for(int i = 0; i < count; i++)
{
Radix(arr, len, i);//i表示从右数第0位开始
}
八、归并排序
时间复杂度:O(nlong2n)
空间复杂度:O(n)
稳定性:稳定
主要思想:
将待排序序列R[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。
图解:
代码:
void Mearge(int *arr,int len,int gap)
{
int *brr = (int *)malloc(sizeof(int)*len);//申请空间
assert(brr != NULL);
int i = 0;//从第一个开始
int s1 = 0;//指向的一个元素
int e1 = s1 + gap - 1;//指向第一个归并段的最后一个元素
int s2 = e1 + 1;//第二段的第一个元素
int e2 = s2 + gap - 1 < len - 1 ? s2 + gap - 1:len - 1;//判断e2的位置 若超出len 则指向len-1
while(s2 < len)//当前有两个归并端
{
while(s1 <= e1 && s2 <= e2)
{
if(arr[s1] < arr[s2])//第一个归并段的开头与第二个归并段的开头比较
{
brr[i++] = arr[s1++];
}
else
{
brr[i++] = arr[s2++];
}
}
while(s1 <= e1)
{
brr[i++] = arr[s1++];
}
while(s2 <= e2)
{
brr[i++] =arr[s2++];
}
s1 = e2 + 1;//第二次重新合并后 重新开始
e1 = s1 + gap - 1;
s2 = e1 + 1;
e2 = s2 + gap - 1 < len ? s2 + gap -1:len - 1;
}
while(s1 < len)
{
brr[i++] = arr[s1++];
}
for(int i = 0;i < len;i++)
{
arr[i] = brr[i];
}
free(brr);
}
void Mearge_Sort(int *arr,int len)
{
for(int i=1;i<len;i*=2)
{
Mearge(arr,len,i);
}
}
总结:
1. 当n比较小时,可以直接采用直接插入排序或者选择排序;
2.若数据初始状态基本有序(正序),选择直接插入排序、冒泡排序或者快速排序为宜;
3.若n比较大,则采用时间复杂度为O(nlong2n)的排序算法:快速排序、堆排序或者归并排序;
4.快速排序是目前基于比较的排序中被认为最好的方法,当待排关键字是随机分布时,快速排序的平均时间最短;
5.堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况,所以这两个排序实际运用的时候可以比较一下,选择最优的;但是这两种排序都是不稳定的。
6.若要求排序稳定,则可以选用归并排序,但是实际运用的时候通常可以将它和直接插入排序结合使用,先利用直接插入排序求得较长有序子序列,然后再两两归并。