八大排序算法
所有的都要烂熟于心,最好最差,平均。只有四个稳定,冒泡 插入 归并 基数 快排最好和平均都是O(NlogN),最差是O(nn),归并和堆排最好 最差 平均都是O(N*logN)
初级排序
冒泡排序
冒泡排序,从前往后就是把最大的冒泡到最后面,缩小区间,或者从最后面开始,每次把最小的冒泡到最前面
两种实现
打印数组的值
#include<stdio.h>
#include<assert.h>
void PrintAr(int* ar, int n)
{
assert(ar != nullptr);
for (int i = 0; i < n; ++i)
{
printf("%5d", ar[i]);
}
printf("\n");
}
交换函数swap
void swap(int* a, int* b) {
assert(a!=nullptr&&b!=nullptr)
int temp = *a;
*a = *b;
*b = temp;
}
这个是从后向前遍历,每次把最小的放到最前面
注意越界条件,j的取值,j+1 时候是否会越界
void bubblesort1(int* ar, int n) {
assert(ar != nullptr);
if (n <= 1)return;
int i, j;
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 2; j >= n - i - 1; j--) {
if (ar[j] > ar[j + 1]) {
swap(&ar[j], &ar[j + 1]);
}
}
}
}
这个是从前往后遍历,每次把最大的放到最后面
注意越界条件,j的取值,j+1 时候会越界
void bubblesort2(int* ar, int n) {
assert(ar != nullptr);
if (n <= 1)return;
int i, j;
for (int i = 0; i < n; i++) {//控制趟数
for (int j = 0; j < n - i-1; j++) {//控制范围
if (ar[j] > ar[j + 1]) {
swap(&ar[j], &ar[j + 1]);
}
}
}
}
冒泡排序的优化算法
这个是从后向前遍历,每次把最小的放到最前面,注意越界条件,j的取值,j+1 时候是否会越界,假如排了几次,发现,已经有序了,就不用再继续排了,直接跳出就行
void bubblesort1plus(int* ar, int n) {
assert(ar != nullptr);
if (n <= 1)return;
int i, j;
for (int i = n - 1; i >= 0; i--) {
bool flag = false;
for (int j = n - 2; j >= n - i - 1; j--) {
if (ar[j] > ar[j + 1]) {
swap(&ar[j], &ar[j + 1]);
flag = true;
}
}
if (flag == false)break;
}
}
选择排序
选择排序,就是每次在指定的区间选择最小的元素,放到最前面
编程要点
先判断指针是否为空,然后判断如果n<=1 则说明一个数据或者没有数据
则不用排序,直接返回,
外层循环只用从0开始遍历到n-2位置,因为最后一个数不用比较,一定是最大的,
如果i本身就是minpos,则不用交换
代码实现
void SelectSort(int* ar, int n)
{
assert(ar != nullptr);
if (n <= 1) return;
for (int i = 0; i < n - 1; ++i)
{
int minpos = i;
for (int j = i + 1; j < n; ++j)
{
if (ar[minpos] > ar[j])
{
minpos = j;
}
}
if (i != minpos)
{
Swap(&ar[minpos], &ar[i]);
}
}
}
插入排序
插入排序,就是遍历每一个数据,将其插入到合适的位置,类似最大堆搜索函数的实现
编程要点
先判断指针是否为空,然后判断如果n<=1 则说明一个数据或者没有数据
//则不用排序,直接返回
//从第二个数据开始,看是否需要向前插入,
//如果大圩前面的数,则直接遍历到第三个数据
//如果小于前面一个数,则需要向前插入,
//插入步骤是,现将该数提取到temp,前面的数下标为j,将前面的数移动到该位置,即ar[j+1]=ar[j] 然后j–;
// 看temp是否能插入到前面的一个位置,如果该temp大于签的的数
// 则可以插入到j+1位置,否则,继续将j位置到j+1位置
//循环的结束条件是temp>ar[j] 和j==-1 则将temp放在j+1位置
代码实现
void InsertSort(int* ar, int n)
{
assert(ar != nullptr);
if (n <= 1) return;
for (int i = 1; i < n; ++i)
{
PrintAr(ar, n);
if (ar[i - 1] > ar[i])
{
int j = i - 1;
int tmp = ar[j + 1];
do
{
ar[j + 1] = ar[j];
j = j - 1;
} while (j >= 0 && ar[j] > tmp);
ar[j + 1] = tmp;
}
}
}
进阶排序
希尔排序
基数(桶)排序
堆排序
注意从第一个非叶节点开始向上调整,非叶节点i=n/2-1;
分析
堆排序的思想就是先将待排序的序列建成大根堆,使得每个父节点的元素大于等于它的子节点。此时整个序列最大值即为堆顶 元素,我们将其与末尾元素交换,使末尾元素为最大值,然后再调整堆顶元素使得剩下的 n−1 个元素仍为大根堆,再重复执行以上操作我们即能得到一个有序的序列
#include<stdio.h>
#include<assert.h>
//打印数组的值
void PrintAr(int* ar, int n)
{
assert(ar != nullptr);
for (int i = 0; i < n; ++i)
{
printf("%5d", ar[i]);
}
printf("\n");
}
//向下搜索调整,start end都是下标
//实现的功能是将ar[start]放到合适的位置
void adjust(int* ar, int start, int end) {
assert(ar != nullptr);
int i = start;
int j = 2 * i + 1;
int temp = ar[i];
while (j <= end) {
if (j < end && ar[j] < ar[j + 1])j++;
if (temp >= ar[j])break;
ar[i] = ar[j];
i = j;
j = 2 * i + 1;
}
ar[i] = temp;
}
//交换数组中的两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//堆排序
//先是建立最大堆,就是从第一个非叶节点 下表为i=n/2-1 n为数组大小
//一直向上调整,
//然后每次将堆顶的最大值, ,每次需要
//重新将顶部调整一下,从0到i-1位置,最大元素不动
void heapsort(int* ar, int n) {
assert(ar != nullptr);
for (int i = n / 2 - 1; i >= 0;i--) {
adjust(ar, i, n - 1);
}
for (int i = n - 1; i > 0;i--) {
swap(&ar[0], &ar[i]);
adjust(ar, 0, i-1);
}
}
int main()
{
int ar[] = { 23,56,90,78,67,100,12,45,34,89 };
int n = sizeof(ar) / sizeof(ar[0]);
heapsort(ar, n);
PrintAr(ar, n);
return 0;
}
用C++语法实现
//堆排序
void adjust(vector<int>& num, int start, int end) {
int i = start;
int j = 2 * i + 1;
int tmp = num[start];
while (j<=end) {
if (j < end && num[j] < num[j + 1])j++;
if (tmp >= num[j])break;
num[i] = num[j];
i = j;
j = 2 * i + 1;
}
num[i] = tmp;
}
void swap(vector<int>& num, int start, int end) {
int tmp = num[start];
num[start] = num[end];
num[end] = tmp;
}
void heapsort(vector<int>&num) {
int n = num.size();
for (int i = n / 2 - 1; i >= 0; i--) {
adjust(num, i, n - 1);
}
for (int i = n-1; i >0; i--) {
swap(num, 0, n - 1-i);
adjust(num, 0, n - i - 2);
}
}
//堆排9:35-55
int main() {
vector<int>num{ 6,2,1,4,5,9,6,3,2,5,4,1,2 };
int n = num.size();
heapsort(num);
for (auto x : num) {
cout << x <<" ";
}
cout << endl;
return 0;
}
归并排序
归并排序利用了分治的思想来对序列进行排序。对一个长为 n 的待排序的序列,我们将其分解成两个长度为n/2的子序列。每次先递归调用函数使两个子序列有序,然后我们再线性合并两个有序的子序列使整个序列有序
递归实现
void PrintAr(int* ar, int n)
{
assert(ar != nullptr);
for (int i = 0; i < n; ++i)
{
printf("%5d", ar[i]);
}
printf("\n");
}
//类似将两个有序的数组合并成一个有序的数组,
//一个数组,pos左边的有序,pos右边的也有序
void MergeAr(int* dest, int* src, int left, int pos, int right) {
assert(dest != nullptr && src != nullptr);
int i = left, j = pos + 1;
int k = left;
while (i <= pos && j <= right) {
dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
}
while (i <= pos) {
dest[k++] = src[i++];
}
while (j<= right) {
dest[k++] = src[j++];
}
}
void CopyAr(int* dest, int* src, int left, int right) {
assert(dest != nullptr && src != nullptr);
for (int i = left; i <= right; i++) {
dest[i] = src[i];
}
}
//int m = left + (right - left) / 2; 不能是left + (right - left+1) / 2;
//每次到(0,1) passsort(ar, br, 0, 1); 会陷入死递归,出不来栈溢出
//55 45 33 22 11 0
//0 1 2 3 4 5
//
//m=2
//55 45 33 | 22 11 0
//0 1 2 | 3 4 5
//m=1
//55 45 | 33 | 22 11 0
//0 1 | 2 | 3 4 5
//m=0
//55 | 45 | 33 | 22 11 0
//0 | 1 | 2 | 3 4 5
//合并 使其有序
//55 | 45
//0 | 1
//45 | 55
//0 | 1
//下一次合并
//45 | 55 | 33
//0 | 1 | 2
//33 | 45 | 55
//0 | 1 | 2
//if (left < right) 不要写成 while(left<right)
//递归到只有一个数据的时候,退出,合并这两个数据,每一个数据自己是有序的,‘
//所以每次递归的底部是,一个数据,即left==right
void passsort(int* br, int* ar, int left, int right) {
assert(ar != nullptr && br != nullptr);
if (left < right) {
int m = left + (right - left) / 2;
passsort(br, ar, left, m);
passsort(br, ar, m + 1, right);
MergeAr(br, ar, left, m, right);
CopyAr(ar, br, left, right);
PrintAr(ar, 10);
}
}
//归并排序
//开辟br存储归并后的ar
void mergesort(int* ar, int n) {
assert (ar != nullptr);
if (n <= 1)return;
int* br = (int*)malloc(sizeof(int) * n);
if (br == nullptr)exit(EXIT_FAILURE);
passsort(br, ar, 0, n - 1);
free(br);
br = nullptr;
}
int main()
{
int ar[] = { 78,12,23,90,100,34,89,45,56,67 };
int n = sizeof(ar) / sizeof(ar[0]);
PrintAr(ar, n);
mergesort(ar, n);
PrintAr(ar, n);
return 0;
}
非递归实现 迭代实现 C++实现
//归并排序递归形式
void merge(vector<int>& nums, int l, int mid, int r) {
int i = l, j = mid + 1;
vector<int>tmp(r - l + 1, 0);
int k = 0;
while (i <= mid && j <= r) {
if (nums[i] <= nums[j]) {
tmp[k] = nums[i];
i++;
}
else {
tmp[k] = nums[j];
j++;
}
k++;
}
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= r) {
tmp[k++] = nums[j++];
}
for (int i = 0, j = l; i < tmp.size(); i++, j++) {
nums[j] = tmp[i];
}
}
void mergesort1(vector<int>& nums, int l, int r) {
if (l < r) {
int mid = (l + r) / 2;
mergesort1(nums, l, mid);
mergesort1(nums, mid+1, r);
merge(nums, l, mid, r);
}
}
//归并排序的非递归形式
void nicepass(vector<int>& nums, int s, int n) {
int i = 0;
for (; i + 2 * s <= n - 1; i = i + 2 * s) {
merge(nums, i, i + s - 1, i + 2 * s - 1);
}
if (i + s <= n - 1) {
merge(nums, i, i + s - 1, n - 1);
}
}
void mergesort2(vector<int>& nums,int n) {
if (n <= 1)return;
int s = 1;
while (s < n) {
nicepass(nums, s, n);
s *= 2;
}
}
int main() {
vector<int>nums{6,2,3,5,1,2,3,6,9 };
int n = nums.size();
mergesort2(nums, n);
for (auto& x : nums) {
cout << x << " ";
}
return 0;
}
快速排序
快速排序的主要思想是通过划分将待排序的序列分成前后两部分,其中前一部分的数据都比后一部分的数据要小,然后再递归调用函数对两部分的序列分别进行快速排序,以此使整个序列达到有序。
递归实现
核心是分治算法的体现
算法要点
partitiaon函数算法要点
//将ar数组的数分成两部分,左边的部分是都是<=ar[i],右边的都是>ar[i]
//大致思路是,在右边开始遍历,找比temp小的数,找到后直接赋值给i下标
// 从左边遍历,找比temp大的数,找到后直接赋值给j下标
// while (i<j && ar[j]>temp)j–;中加条件i<j是因为,假如上一步i j就差一步
//j–后,i=j,就不用比较了,加入条件,if(i<j).在进行赋值,不然就是自己给自己赋值
//while (i < j && ar[i] <= temp)i++;,如果不加i<j,i++,j 和 i 就会错位
//导致i>j 就会出错
int Parition(int* ar, int left, int right) {
assert(ar != nullptr);
int i = left, j = right;
int temp = ar[i];
while (i < j) {//i==j就会退出
while (i<j && ar[j]>temp)j--;
if(i<j)ar[i] = ar[j];
while (i < j && ar[i] <= temp)i++;
if(i<j)ar[j] = ar[i];
}
ar[i] = temp;
return i;
}
//不断地缩小区间,每次看pos,pos左边都小于等于ar[pos]
//右边都大于ar[pos] 就算是两个数,也要排序,所以,当left==right
//说明这一边只有一个数了,就不用再递归排序了
快排优化 三数取中
第一个数,最后一个数,中间的数,将中位数置换到数组开头。
//快排优化
void swap(vector<int>& num, int l, int r) {
int tmp = num[l];
num[l] = num[r];
num[r] = tmp;
}
void setmid(vector<int>num, int l, int mid, int r) {
if (num[mid] > num[r]) {
swap(num[mid], num[r]);
}
if (num[l] < num[mid]) {
swap(num[l], num[mid]);
}
if (num[l] > num[r]) {
swap(num[l], num[r]);
}
}
int parition2(vector<int>& num, int l, int r) {
int i = l, j = r;
setmid(num, l, (l + r) / 2, r);
int tmp = num[l];
while (i < j) {
while (i<j && num[j]>tmp)j--;
if (i < j)num[i] = num[j];
while (i < j && num[i] <= tmp)i++;
if (i < j)num[j] = num[i];
}
num[i] = tmp;
return i;
}
代码实现
类似二叉树的前序遍历,二叉树是打印节点值,这里是调整一半,
结束条件:二叉树是该节点为空,快排是left==right 只有一个数
void Qsort(int* ar, int left, int right) {
assert(ar != nullptr);
if (left < right) {//如果left==right 只有一个数,就不用排序了
int pos = Parition(ar, left, right);
Qsort(ar, left, pos - 1);
Qsort(ar, pos + 1, right);
}
}
void quicksort1(int* ar, int n) {
assert(ar != nullptr);
Qsort(ar, 0, n - 1);
}
非递归实现
编程要点
//非递归的排序算法 快排 需要用到队列,即链式队列,先进先出
//队列为空时说明排序完成,退出
//队列不为空时,就出队,将其当做一个区间,算出中间的pos,然后再将两个区间入队
//如果区间为pos + 1 == right即右边只有一个数据,就不要继续排序,不用入队,
//pos - 1 > left pos位置是,它左边都小于等于他自己,如果pos - 1 = left,左边只有一个数,不用入队
代码实现
#include<queue>
queue<int>qu;
void quicksort2(int* ar, int n) {
assert(ar != nullptr);
if (n <= 1)return;
std::queue<int>qu;
qu.push(0);
qu.push(n - 1);
int left, right;
while (1) {
if (qu.empty())break;
left = qu.front();
qu.pop();
if (qu.empty())break;
right = qu.front();
qu.pop();
int pos = Parition(ar, left, right);
if (pos - 1 > left) {
qu.push(left);
qu.push(pos-1);
}
if (pos + 1 < right) {
qu.push(pos+1);
qu.push(right);
}
}
}
//67, 56, 90, 78, 44
//0 1 2 3 4
// 排序一次后
//44, 56, 67, 78, 90
//0 1 2 3 4
//队列的过程为
// 0 4
// 0 4 | 0 2 3 4
// 0 4 | 0 2 | 3 4 1 2
// 0 4 | 0 2 | 3 4 | 1 2
// 0 4 | 0 2 | 3 4 | 1 2 | 退出,排序完成
C++实现
//快排优化
void swap(vector<int>& num, int l, int r) {
int tmp = num[l];
num[l] = num[r];
num[r] = tmp;
}
void setmid(vector<int>num, int l, int mid, int r) {
if (num[mid] > num[r]) {
swap(num[mid], num[r]);
}
if (num[l] < num[mid]) {
swap(num[l], num[mid]);
}
if (num[l] > num[r]) {
swap(num[l], num[r]);
}
}
int parition2(vector<int>& num, int l, int r) {
int i = l, j = r;
setmid(num, l, (l + r) / 2, r);
int tmp = num[l];
while (i < j) {
while (i<j && num[j]>tmp)j--;
if (i < j)num[i] = num[j];
while (i < j && num[i] <= tmp)i++;
if (i < j)num[j] = num[i];
}
num[i] = tmp;
return i;
}
int parition1(vector<int>&num, int l, int r) {
int i = l, j = r;
int tmp = num[i];
while (i < j) {
while (i<j && num[j]>tmp)j--;
if (i < j)num[i] = num[j];
while (i < j && num[i] <= tmp)i++;
if (i < j)num[j] = num[i];
}
num[i] = tmp;
return i;
}
void quicksort1(vector<int>& num, int l, int r) {
if (l < r) {
int mid = parition2(num, l, r);
quicksort1(num, l, mid);
quicksort1(num, mid + 1, r);
}
}
void quicksort2(vector<int>&num, int l, int r) {
std::queue<int>qu;
qu.push(l);
qu.push(r);
while (1) {
if (qu.empty())break;
int left = qu.front(); qu.pop();
if (qu.empty())break;
int right = qu.front(); qu.pop();
int mid = parition2(num, left, right);
if (mid - 1 > left) {
qu.push(left);
qu.push(mid);
}
if (mid + 1 < right) {
qu.push(mid + 1);
qu.push(right);
}
}
}
int main() {
vector<int>num{ 2,6,1,7,3 };
int n = num.size();
quicksort2(num, 0, n - 1);
for (auto& x : num) {
cout << x;
}
cout << endl;
return 0;
}