1.基数排序(桶排序):低位优先,所有数据从低位开始,一次放入到10个桶内,再次从桶里取出,直到完全有序。
快速排序:每次找基准值的位置,以基准线为分界线,左边的值全部比基准值小,而右边的值肯定比基准值大。
规则:从右向左找比基准值小的数据放到左边,找到后放到左边,从左向右找比基准值大的数据,找到后放到右边,重复上面的操作,直到left=right,循环退出,再将基准值放到arr[left]或者arr[right]。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
//八大排序 排序分为升序和降序 我们默认使用升序
//算法的描述 算法的实现 算法的评价(时间复杂度,空间复杂度,稳定性)
//什么是稳定性:如果排序前A在A`的前面,排序之后A孩子A`的前面,则排序算法稳定
//如何判断其稳定性:看算法中是否存在跳跃交换
//第一个排序算法:直接插入排序:每次从待排序队列中取一个值,放到已排序好的队列,再次保持有序,重复这样的动作,直到把待排序队列中的值全部取完 时间复杂度O(n^2) 空间复杂度O(1) 稳定的
//第二个排序算法:希尔排序(缩小增量排序):是一个特殊的直接插入排序,相当于多次调用直接插入排序,每一次的增量保持互素,并且最后一个增量一定位1,为1才能保证其完全有序 时间复杂度O(n^1.3~1.5) 空间复杂度O(1) 不稳定
//第三个排序算法:冒泡排序(沉石排序):两两比较,大的向后挪动,小的向前 时间复杂度O(n^2) 空间复杂度O(1) 稳定的(不存在跳跃交换)
//第四个排序算法:二路归并排序(非递归形式):两两合并成一个组,当合并后的组能容纳arr所有值的时候,则退出,因为此时已经组内完全有序 时间复杂度O(nlogn) 空间复杂度O(nlogn) 稳定
//第五个排序算法:简单选择排序:每一次将待排序序列中最小值和待排序序列中第一个值进行交换 直到完全有序即可 时间复杂度O(n^2) 空间复杂度O(1) 不稳定
//第六个排序算法:堆排序:将一维数组臆想成一个完全二叉树,再将其调整为大顶堆,再将根节点和尾节点进行交换,再次进行调整,这样循环往复,直至将其调整为完全有序 时间复杂度O(nlogn) 空间复杂度O(1) 不稳定
//第七个排序算法:基数排序(桶排序):低位优先,所有数据从低位开始,一次放入到10个桶内,再次从桶里取出,直到完全有序。时间复杂度O(dn),空间复杂度O(n)
//第八个排序算法:快速排序:从右向左找比基准值小的数据放到左边,找到后放到左边,从左向右找比基准值大的数据,找到后放到右边,重复上面的操作,直到left=right,循环退出,再将基准值放到arr[left]或者arr[right]。时间复杂度O(nlogn),空间复杂度O(nlogn),不稳定
void InsertSort(int arr[], int len)
{
assert(arr != NULL);
if (arr == NULL)
{
return;
}
int count = 0;
int tmp;
int j;//将j的生存周期提高,保证break后的的代码arr[j+1] = tmp;有效
for (int i = 1; i < len; i++)//每次从待排序队列中取的值
{
tmp = arr[i];//用tmp保存待插入的值
for (j = i - 1; j >= 0; j--)//从右向左找不比tmp大的值
{
if (arr[j] > tmp)//如果比tmp大 则向右放一格
{
arr[j + 1] = arr[j];
count++;
}
else//如果不比tmp大
{
break;
}
}
arr[j + 1] = tmp;
}
printf("swap: %d\n", count);
}
void Show(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int count2 = 0;
static void Shell(int arr[], int len, int gap)
{
int tmp;
int j;//将j的生存周期提高,保证break后的的代码arr[j+1] = tmp;有效
for (int i = gap; i < len; i++)//每次从待排序队列中取的值
{
tmp = arr[i];//用tmp保存待插入的值
for (j = i - gap; j >= 0; j = j - gap)//从右向左找不比tmp大的值
{
if (arr[j] > tmp)//如果比tmp大 则向右放一格
{
arr[j + gap] = arr[j];
count2++;
}
else//如果不比tmp大
{
break;
}
}
arr[j + gap] = tmp;
}
}
void ShellSort(int arr[], int len)
{
//assert
int gap[] = {
5, 3, 1 };
int lengap = sizeof(gap) / sizeof(gap[0]);
for (int i = 0; i < lengap; i++)
{
Shell(arr, len, gap[i]);//5 3 1
}
}
void BubbleSort(int arr[], int len)
{
bool tag = true;//优化标记 如果当前这轮存在一次交换 则tag变成FALSE
//当tag为true不就代表着不存在前面比后面大
//则已经完全有序 那直接退出即可 剩余轮次不需要执行
int count = 0;
for (int i = 0; i < len - 1; i++)//少一轮
{
tag = true;
for (int j = 0; j + 1 < len - i; j++)//j<len-1-i
{
if (arr[j] > arr[j + 1])//两两比较 前面大于后面 则交换两个值
{
tag = false;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
count++;
if (tag)
{
break;
}
}
printf("跑了%d趟\n", count);
}
//一次融合的代码 时间复杂度O(n)
static void Merge(int arr[], int len, int gap)//gap->几几合并的几
{
int* brr = (int*)malloc(sizeof(int) * len);//申请额外的辅助空间brr
assert(brr != NULL);
int i = 0;//i指向brr的下标
//申请四个指针 用low1 high1来表示左边的组的边界 用low2 high2来表示右边的组的边界
int low1 = 0;
int high1 = low1 + gap - 1;
int low2 = high1 + 1;
int high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;
//low2+gap-1是我应得的 len如果大于我应得的 则该给我多少就给多少 如果小则将你剩余的都给我
while (low2 < len)//我抓的两个组都存在 low2<len则代表右手没抓空 而右手没抓空 则左手肯定也没抓空 则证明两手都抓中了
{
while (low1 <= high1 && low2 <= high2)//两个组都还有数据 则接着比较向下取到brr内
{
if (arr[low1] <= arr[low2])
{
brr[i++] = arr[low1++];
}
else
{
brr[i++] = arr[low2++];
}
}
//此时肯定有一组数据没了 那么将另一组数据直接挪到brr内即可
//但此时需要判断左右手哪个抓的组里没数据了
while (low1 <= high1)//左手抓的组里面还有数据 在直接挪到brr内
{
brr[i++] = arr[low1++];
}
while (low2 <= high2)//右手抓的组里面还有数据 在直接挪到brr内
{
brr[i++] = arr[low2++];
}
low1 = high2 + 1;
high1 = low1 + gap - 1;
low2 = high1 + 1;
high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;
}
//此时肯定不能同时两手都抓到组 而且可以得到一个信息 那就是(只可能右手抓的组是空的)
//左手抓上一个组 右手抓空 接下来只需要将左手的组原样挪下来即可
while (low1 < len)//左手抓的组里面还有数据 在直接挪到brr内
{
brr[i++] = arr[low1++];
}
//最后一步:将辅助空间brr里的数据全部重新拷贝到arr里
for (int i = 0; i < len; i++)
{
arr[i] = brr[i];
}
free(brr);//最后记得将malloc来的brr释放掉 避免内存泄露
brr = NULL;
}
void MergeSort(int arr[], int len)
{
//assert
for (int i = 1; i < len; i *= 2)//11融合 22融合 44融合 88融合 直到融合后的组包含了arr所有值
{
Merge(arr, len, i);//logn次 * O(n) = nlogn
}
}
//时间复杂度 O(n^2) 空间复杂度O(1) 稳定吗? 不稳定
void SelectSort(int* arr, int len)
{
//assert
int minindex;//minindex 保存的是最小值的下标
for (int i = 0; i < len - 1; i++)//趟数 总数少一趟
{
minindex = i;//每趟循环一进来 我们首先认为待排序序列的第一个值是最小值 所以minindex先赋值为i
for (int j = i + 1; j < len; j++)//从待排序序列的第二个值开始和arr[minindex]比较
{
if (arr[j] < arr[minindex])//如果找到更小的数 则minindex重新赋值为新的最小值的下标
{
minindex = j;
}
}
//此时for循环执行结束 代表着minindex保存的就是最小值的下标
//直接进行交换
if (i != minindex)
{
int tmp = arr[i];//因为i保存的是待排序序列第一个值
arr[i] = arr[minindex];
arr[minindex] = tmp;
}
}
}
void HeapAdjust(int arr[], int start, int end)
{
int tmp = arr[start];
for (int i = start * 2 + 1; i <= end; i = start * 2 + 1)//语句3 精髓 时间复杂度O(nlogn)的原因
{
if (i < end && arr[i] < arr[i + 1])//有右孩子 并且右孩子比左孩子大
{
i++;
}
//如果if为假 代表要么右孩子不存在 要么右孩子存在 但是小于左孩子
//此时 i保存的是左右孩子中较大的值的下标
//接着让父子比较
if (arr[i] > tmp)
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
//此时for循环推出了,要么此时触底 要莫if(arr[i]>tmp)为假 break跳出循环
arr[start] = tmp;
}
void HeapSort(int arr[], int len)
{
//1.先调整成大顶堆
for (int i = (len - 1 - 1) / 2; i >= 0; i--)//O(n)
{
HeapAdjust(arr, i, len - 1);//(logn)
}//两个一乘 则时间复杂度O(nlogn)
//根节点和尾节点交换
for (int i = 0; i < len - 1; i++)//O(n)
{
int tmp = arr[0];
arr[0] = arr[len - 1 - i];//9 8 7 6 5 4 3 2 1
arr[len - 1 - i] = tmp;
HeapAdjust(arr, 0, (len - 1 - i) - 1);//(len-1-i)相当于最后一个节点的下标 然后最后一个节点不参与计算 则再-1
}
}
static int Get_figure(int* arr, int len)
{
assert(arr != NULL);
int tmp = 0;
for (int i = 0; i < len; i++)
{
if (arr[i] > tmp)
{
tmp = arr[i];
}
}
//获取tmp的位数
int count = 0;//保存位数
while (tmp != 0)
{
count++;
tmp /= 10;
}
}
//以fin的规则取n对应的值
//123 0->3
//234 2->2
//345 3->0
//12345 3->12345>1234
static int Get_Num(int n, int fin)
{
for (int i = 0; i < fin; i++)
{
n /= 10;
}
return n%10;
}
//作业1.不以二位静态数组,而是使用队列形式去实现
//以fin为排序一次,入队,放到桶内,再次取出
static void Radix(int* arr, int len, int fin)
{
int bucket[10][20] = {
0 };//十个桶,每个桶初始容量20
int num[10] = {
0 };//八寸每一个桶的有效值个数
for (int i = 0; i < len; i++)//从0号下标开始依次放入桶内
{
int index = Get_Num(arr[i], fin);//获取下一个 arr[i]放入哪一个桶内
arr[index][bucket[index]] = arr[i];//放入对应桶内
num[index]++;//对应桶内个数++
}
//此时for循环执行结束,代表数据全部放入桶内
int k=0;
//接着应该从0-9桶内依次取出数据
for (int i = 0; i <= 9; i++)//i保存的是桶号,10 O(n)
{
for (int j = 0; j<num[i]; j++)//j代表从0号下标开始取数据,取完后
{
arr[k++] = bucket[i][j];
}
}
}
void RadixSort(int arr[], int len)
{
int count = Get_figure(arr, len);//d 代表最大值位数
for(int i = 0; i < count; i++)
{
Radix(arr, len, i);
}
}
//***(面试笔试常考)
static int Partition(int* arr, int left, int right)
{
assert(arr != NULL);
//保证基准值在tmp里面
int tmp = arr[left];
while (left < right)//保证最少有两个值
{
while (left<right && arr[right]>tmp) right--;
if (left == right)
{
break;
}
arr[left] = arr[right];
while (left<right && arr[right]<=tmp) left++;
if (left == right)
{
break;
}
arr[right] = arr[left];
}
arr[left] = tmp;
return left;
}
static void Quick(int* arr, int left, int right)
{
//第一个优化:如果right-left判断出数据量特别小,直到改道去执行冒泡或者直接插入算法
//第二个优化:三数取中,去取第一个值和最中间的值和最后一个值判断下一个找一个不大不小的数值当作基准线
assert(arr != NULL);
if (left < right)
{
int par = Partition(arr, left, right);
if (left < par - 1)//保证基准值左边数据最少有两个
{
Quick(arr, left, par - 1);
}
if (par + 1 < right)//保证基准值右边数据最少有两个
{
Quick(arr, par + 1, right);
}
}
}
void QuickSort(int arr[], int len)
{
assert(arr != NULL);
Quick(arr, 0, len - 1);
}
//作业2:用非递归的形式实现快速排序(栈)
int main()
{
int arr[] = {
1,3,2,4,6,5,7,9,8 };
int len = sizeof(arr) / sizeof(arr[0]);
//InsertSort(arr, len);
//ShellSort(arr, len);
//printf("swap = %d\n", count2);
//BubbleSort(arr, len);
//MergeSort(arr, len);
//SelectSort(arr, len);
//HeapSort(arr, len);
RadixSort(arr, len);
Show(arr, len);
return 0;
}