目录
1、冒泡排序
基本思路:
相邻元素两两比较,满足条件就交换,不满足就直接下一位,每次循环确定一位最大或最小的元素。
动图如下:
代码如下:
void BubbleSort(int* arr, int n)//n是数组长度
{
for (int end = n - 1; end > 0; end--)
{
int flag = 0;//标记单个循环内是否进行了交换
for (int i = 0; i < end; i++)
{
//相邻两元素,前者比后者大,交换(升序)
if (arr[i] > arr[i + 1])
{
int temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
flag = 1;
}
//如果没交换,说明已经有序,直接跳出
if (flag == 0)
{
break;
}
}
}
}
2、选择排序
基本思路:
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。
动图如下:
代码如下:
void SelectSort(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
int min = i;//记录最小值的下标
for (int j = i + 1; j < n; j++)
{
//如果arr[j]的值比arr[min]小,更新min的值
if (arr[min] > arr[j])
{
min = j;
}
}
//第i个位置的值与其后面元素中的最小值进行交换
if (i != min)
{
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
3、插入排序
基本思路:
和抓扑克牌一个道理,第一个看作有序,其后面的看作无序,抓一张新的和前面的进行比较,找到合适的位置插入,前面有序的就多一个,后面无序的就少一个。以此类推直到无序的为个数为0。
动图如下:
代码如下:
void InsertSort(int* arr, int n)
{
for (int i = 0; i < n - 1; ++i)
{
// [0,end] 插入 end+1 [0, end+1]有序
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
if (arr[end] >= tmp)
{
arr[end + 1] = arr[end];
--end;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
4、希尔排序
基本思路:
就是进行多次间隔距离为gap(递减)的插入排序,最后直到间隔距离为1的时候就是原原本本的插入排序,先前间隔距离大于一的插入排序称之为预排序。
动图如下:
代码如下:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 2;
// [0,end] 插入 end+gap [0, end+gap]有序 -- 间隔为gap的数据
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
5、堆排序
堆的性质:
a、堆中某节点的值总是不大于或者不小于其父节点的值。
b、堆总是一个完全二叉树。
动图如下:
代码如下:
a、建堆
升序建大堆,降序建小堆
void AdjustDown(int *arr, int parent, int n)
{
//暂定左孩子为较小的孩子
int minChild = parent * 2 + 1;
while (minChild < n)
{
//选出较大的那个孩子
if (minChild < n && arr[minChild + 1] > arr[minChild])
{
minChild++;
}
//小的往下面往下面沉,大的往上浮
if (arr[parent] < arr[minChild])
{
int temp = arr[parent];
arr[parent] = arr[minChild];
arr[minChild] = temp;
parent = minChild;
minChild = parent * 2 + 1;
}
else
{
break;
}
}
}
// 升序 建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDwon(a, n, i);
}
b、排序
在已经建好堆的基础上,每次循环将堆顶的元素和最后一个元素交换,这样最大或者最小的就在尾部,然后将计算的元素个数减一,循环至只剩下一个元素,就已经排好序了。
void HeapSort(int* a, int n)
{
// 依次选数,从后往前排
// 升序 -- 大堆
// 降序 -- 小堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
int i = 1;
while (i < n)
{
Swap(&a[0], &a[n - i]);
AdjustDown(a, n - i, 0);
++i;
}
}
6、归并排序
基本思路:
a、将整个待排序序列划分成多个不可再分的子序列,每个子序列中仅有 1 个元素;
b、所有的子序列进行两两合并,合并过程中完成排序操作,最终合并得到的新序列就是有序序列。
动图如下:
代码如下:
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = (end + begin) / 2;
// [begin, mid] [mid+1, end]
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1, end, tmp);
// 归并 取小的尾插
// [begin, mid] [mid+1, end]
int begin1 = begin, end1 = mid;
int begin2 = mid+1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
memcpy(a+begin, tmp+begin, (end-begin+1)*sizeof(int));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
非递归就是模拟递归后半阶段:
需要注意的就是边界的调整:
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
int gap = 1;
while (gap < n)
{
// gap个数据 gap个数据归并
for (int j = 0; j < n; j += 2 * gap)
{
// 归并 取小的尾插
int begin1 = j, end1 = j + gap - 1;
int begin2 = j + gap, end2 = j + 2 * gap - 1;
// 第一组越界
if (end1 >= n)
{
break;
}
// 第二组全部越界
if (begin2 >= n)
{
break;
}
// 第二组部分越界
if (end2 >= n)
{
// 修正一下end2,继续归并
end2 = n - 1;
}
int i = j;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
memcpy(a+j, tmp+j, (end2-j+1)*sizeof(int));
}
gap *= 2;
}
free(tmp);
tmp = NULL;
}
7、快速排序
基本思路:
再待排序的元素中选取一个基准(key),把比基准小的值放在其左边,其他的放在其右边。
7.1 hoare法
动图如下:
代码如下:
int PartSort1(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
//printf("[%d,%d]-%d\n", left, right, mid);
Swap(&a[left], &a[mid]);
int keyi = left;
while (left < right)
{
// 6 6 6 6 6
// R找小
while (left < right && a[right] >= a[keyi])
{
--right;
}
// L找大
while (left < right && a[left] <= a[keyi])
{
++left;
}
if (left < right)
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[meeti], &a[keyi]);
return meeti;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort1(a, begin, end);
//递归区间[begin, keyi-1] keyi [keyi+1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
7.2 挖坑法
动图如下:
代码如下:
int PartSort2(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int key = a[left];
int hole = left;
while (left < right)
{
// 右边找小,填到左边坑
while (left < right && a[right] >= key)
{
--right;
}
a[hole] = a[right];
hole = right;
// 左边找大,填到右边坑
while (left < right && a[left] <= key)
{
++left;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort2(a, begin, end);
//递归区间[begin, keyi-1] keyi [keyi+1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
7.3 前后指针
动图如下:
代码如下:
int PartSort3(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int keyi = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
// 找小
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[cur], &a[prev]);
++cur;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort3(a, begin, end);
//递归区间[begin, keyi-1] keyi [keyi+1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
7.4优化思路
a、三数取中
快速排序的时候,取key值的时候每次都是最小或者最大的,那么每次递归的就再key的左边或者右边递归,在这种情况下时间复杂度接近O(n^2);
代码如下:
int GetMidIndex(int* a, int left, int right)
{
int mid = left + (right - left) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
{
return right;
}
}
else // a[left] >= a[mid]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
}
b、插入排序
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
if (end - begin <= 8)
{
InsertSort(a + begin, end - begin + 1);
}
else
{
int keyi = PartSort(a, begin, end);
//[begin, keyi-1] keyi [keyi+1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
}