排序整理(c++实现),搭配图解
十大排序算法
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
元素的移动次数与关键字的初始排列次序无关的是:基数排序
元素的比较次数与初始序列无关是:选择排序
算法的时间复杂度与初始序列无关的是:选择排序
1.快速排序
方法
假如我们要排序这几个数 6 1 2 7 9 3 4 5 10 8
我们都是先找左边第一个数用作一开始作比较的基数就是6,然后右边开始比较,假设左边的哨兵是i,右边的是j。
从j开始找到比6小的第一个数,停下来。在从左边的i开始找,找到第一个比6大的书停下来。最后是i停到了7,停在了5上面。
然后对两个哨兵进行交换
第一次交换结束
然后在按照刚刚的顺序,j在向左寻找,找到4比6小,i向右寻找,找到9比6大,之后交换
第二次结束
j在向左,找到3比6小,但是当i移动的时候两个哨兵相遇了,所以本次循环停止。停在3上面。最后在把3和基准数6交换之后完成。
以6为分界线,对左右边再次调用快速排序进行递归,算法完成
代码:
从小到大
//快速排序(从小到大)
void quickSort(int left, int right, vector<int>& arr)
{
if(left >= right)
return;
int i, j, base, temp;
i = left, j = right;
base = arr[left]; //取最左边的数为基准数
while (i < j)
{
while (arr[j] >= base && i < j)
j--;
while (arr[i] <= base && i < j)
i++;
if(i < j)
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//基准数归位
arr[left] = arr[i];
arr[i] = base;
quickSort(left, i - 1, arr);//递归左边
quickSort(i + 1, right, arr);//递归右边
}
从大到小
//快速排序(从大到小)
void quickSort(int left, int right, vector<int>& arr)
{
if(left >= right) //递归边界条件
return;
if(left < 0 || right >= arr.size())
{
cout << "error args! array bound." << endl;
return;
}//非法输入判断,防止数组越界
int i, j, base, temp;
i = left, j = right;
base = arr[left]; //取最左边的数为基准数
while (i < j)
{
while (arr[j] <= base && i < j)
j--;
while (arr[i] >= base && i < j)
i++;
if(i < j)
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//基准数归位
arr[left] = arr[i];
arr[i] = base;
quickSort(left, i - 1, arr);//递归左边
quickSort(i + 1, right, arr);//递归右边
}
2.冒泡排序
方法
冒泡其实很好理解
冒泡就是把最大的东西放在后面(小到大)或把最小的放在后面(大到小)
方法就是在一次循环中遍历找到最大的数放到后面,找到一个比前面大的数就进行交换,
在从剩下的数中在重发刚刚的循环。完成
代码:
//冒泡排序
void BubbleSort(int* h, size_t len)
{
if(h==NULL) return;
if(len<=1) return;
int temp;
//i是次数,j是具体下标
for(int i=0;i<len-1;++i)
{
for(int j=0;j<len-1-i;++j)
{
if(h[j]>h[j+1])
{
temp = h[j];
h[j+1] = h[j];
h[j] = temp;
}
}
}
}
3.选择排序
方法:
选择排序和冒泡的区别就是,他是最后交换,所以比冒泡做了优化,减少了交换次数。
方法就是用一个数做基准数,在一次循环中找到一个最大或最小的数,然后与最左或最右的数进行交换。
每次循环用相同方法。完成
代码
//选择排序
void SelectionSort(int* h, size_t len)
{
if(h==NULL) return;
if(len<=1) return;
int minindex,i,j,temp;
//i是次数,也即排好的个数;j是继续排
for(i=0;i<len-1;++i)
{
minindex=i;
for(j=i+1;j<len;++j)
{
if(h[j]<h[minindex]) minindex=j;
}
temp = h[j];//最后进行交换
h[j+1] = h[j];
h[j] = temp;
}
}
4.插入排序
方法
我的方法就是把要排列的数组看成两个数组
排序好的,和没有加入排序的。
每次从没有排序好的那一部分中取出一个数,和排序好的做比较,不符合要求就把数字向后移动一位留出空间,符合要求就进行插入操作。
void InsertSort(int a[], int len)
{
int i, j, k;
int tmp;
for (i = 1; i < len; i++) {
k = i; //待插入元素位置
tmp = a[k]; //先拿出来
for (j = i - 1; (j >= 0) && (a[j] > tmp); j--){
a[j + 1] = a[j]; //只要大,则元素后移
k = j; //记录移动的位置
}
a[k] = tmp; //元素插入
}
}
5.归并排序
方法
先把一个数组的所有的数字分成一半,一半,再一半,最后临时开辟的空间中排序合并。
void MergeArray(int* arr, size_t left, size_t mid, size_t right, int* temp)
{
if(arr==NULL) return;
size_t i=left,j=mid+1,k=0;
while(i<=mid && j<=right)
{
if(arr[i]<=arr[j])
{
temp[k++]=arr[i++];
continue;
}
temp[k++]=arr[j++];
}
//将左边剩余元素填充进temp中
while(i<=mid)
temp[k++]=arr[i++];
//将右边子数组剩余部分填充到temp中
while(j<=right)
temp[k++]=arr[j++];
//将融合后的数据拷贝到原来的数据对应的子空间中
memcpy(&arr[left],temp,k*sizeof(int));
}
//归并排序
void MMergeSort(int* arr, size_t left, size_t right, int* temp)
{
if(left<right)
{
//分治
size_t mid=(left+right)/2;
MMergeSort(arr, left, mid, temp);
MMergeSort(arr, mid+1,right, temp);
MergeArray(arr,left, mid, right, temp);
}
}
//void MergeSort(int* h, size_t len)
//{
//if(h==NULL) return;
//if(len<=1) return;
//int* temp=(int*)malloc(len,sizeof(int));
//MMergeSort(h, 0, len-1, temp);
//memcpy(h,temp,sizeof(int)*len);
//free(temp);
//}
6.希尔排序(就是分组的插入排序)
就是通过分组,再把分组里的书进行插入算法,这样比较的次数可能会减少。
分组一般是总数除2
void ShellSort(int a[], int len)
{
int i, j, k, tmp;
int gap = len;
do{
//gap的选择可以有多中方案,如gap = gap/2,这里使用的是业界统一实验平均情况最好的,收敛为1
gap = gap / 2;
for (i = gap; i < len; i += gap) //分成len/gap组
{
//每组使用插入排序
k = i;
tmp = a[k];
for (j = i - gap; (j >= 0) && (a[j] > tmp); j -= gap){
a[j + gap] = a[j];
k = j;
}
a[k] = tmp;
}
} while (gap > 1);
}
7.堆排序(用到完全二叉树)
堆分为两类:
1、最大堆(大顶堆):堆的每个父节点都大于其孩子节点;
2、最小堆(小顶堆):堆的每个父节点都小于其孩子节点;
堆的存储:
一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如下图所示:
实现图解
// 从小到大排序
void Down(int array[], int i, int n) { // 最后结果就是大顶堆
int temp;
int parent = i; // 父节点下标
int child = 2 * i + 1; // 子节点下标
while (child < n) {
if (child + 1 < n && array[child] < array[child + 1]) { // 判断子节点那个大,大的与父节点比较
child++;
}
if (array[parent] < array[child]) { // 判断父节点是否小于子节点
temp = array[parent];
array[parent] = array[child];
array[child] = temp; // 交换父节点和子节点
parent = child; // 子节点下标 赋给 父节点下标
}
child = child * 2 + 1; // 换行,比较下面的父节点和子节点
}
}
void BuildHeap(int array[], int size) {
for (int i = size / 2 - 1; i >= 0; i--) { // 倒数第二排开始, 创建大顶堆,必须从下往上比较
Down(array, i, size); // 否则有的不符合大顶堆定义
}
}
void HeapSort(int array[], int size) {
BuildHeap(array, size); // 初始化堆
for (int i = size - 1; i > 0; i--) {
temp = array[0];
array[0] = array[i];
array[i] = temp; // 交换顶点和第 i 个数据
// 因为只有array[0]改变,其它都符合大顶堆的定义,所以可以从上往下重新建立
Down(array, 0, i); // 重新建立大顶堆
}
}
桶排序
其实桶排序也有插入排序的意思,他只是把插入排序变成分组的罢了。
原理
假如有途中这么几个数要排序。我们假定要分成5个桶进行排序。先找到最大数和最小数是7,110。所以一组的区间就是(110-7)/5=28.8的大小。
然后就是把数据插入桶中,比如第一个是7,就直接插入7属于的第0个区间。加入桶不是空的,插入数据的时候就要使用插入排序的操作。使桶里面的数据有序。
最后在把不为空的桶连接起来。排序完成。
#include <stdio.h>
#include <stdlib.h>
//链表结点描述
typedef struct Node{
double key;
struct Node * next;
}Node;
//辅助数组元素描述
typedef struct{
Node * next;
}Head;
void bucketSort(double* a,int n)
{
int i,j;
Head head[10]={NULL};
Node * p;
Node * q;
Node * node;
for(i=0;i<=n;i++){
node=(Node*)malloc(sizeof(Node));
node->key=a[i];
node->next=NULL;
p = q =head[(int)(a[i]*10)].next;
if(p == NULL){
head[(int)(a[i]*10)].next=node;
continue;
}
while(p){
if(node->key < p->key)
break;
q=p;
p=p->next;
}
if(p == NULL){
q->next=node;
}else{
node->next=p;
q->next=node;
}
}
j=0;
for(i=0;i<10;i++){
p=head[i].next;
while(p){
a[j++]=p->key;
p=p->next;
}
}
}
int main(int argc, char* argv[])
{
int i;
double a[13]={0.5,0.13,0.25,0.18,0.29,0.81,0.52,0.52,0.83,0.52,0.69,0.13,0.16};
bucketSort(a,12);
for(i=0;i<=12;i++)
printf("%-6.2f",a[i]);
printf("\n");
return 0;
}
计数排序
这是一种特殊的排序,加入知道要排序的数组在一个什么区间,通过计数排序,也算是投机取巧的方式。这种方式最快。
方法
假如排序的数载0~10之间,分别是9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9。
一开始大小为11的数组,全部初始化为0
遍历数组,如果遍历到4,就在下标为4的数字+1,记录数字的个数
最后按照数组个数,直接输出排序后的数组。
#include <stdio.h>
#include <stdlib.h>
#define random(x) rand()%(x)
#define NUM 100 // 产生100个随机数
#define MAXNUM 200 //待排序的数字范围是0-200
void countingSort(int A[], int n, int k){
int *c, *b;
int i;
c = (int *)malloc(sizeof(int)*k);/*临时数组,注意它的大小是待排序序列中值最大的那个。如假定该排序序列中最大值为1000000,则该数组需要1000000*sizeof(int)个存储单元*/
b = (int *)malloc(sizeof(int)*n); /*存放排序结果的数组*/
for (i = 0; i < k; i++)
c[i] = 0; /*初始化*/
for (i = 0; i < n; i++)
c[A[i]] += 1; /*统计数组A中每个值为i的元素出现的次数*/
for (i = 1; i < k; i++)
c[i] = c[i - 1] + c[i]; /*确定值为i的元素在数组c中出现的位置*/
for (i = n - 1; i >= 0; i--)
{
b[c[A[i]] - 1] = A[i]; /*对A数组,从后向前确定每个元素所在的最终位置;*/
c[A[i]] -= 1;
}
for (i = 0; i < n; i++)
A[i] = b[i]; /*这个目的是返回A数组作为有序序列*/
free(c);
free(b);
}
void printArray(int A[], int n){
int i = 0;
for (i = 0; i < n; i++){
printf("%4d", A[i]);
}
printf("\n");
}
/*测试*/
int main()
{
int A[NUM];
int i;
for (i = 0; i < NUM; i++)
A[i] = random(MAXNUM);
printf("before sorting:\n");
printArray(A, NUM);
countingSort(A, NUM, MAXNUM);
printf("after sorting:\n");
printArray(A, NUM);
return 0;
}
基数排序
原理
基数排序其实跟计数排序差不多,只要理解了计数排序,基数排序就其实一样
首先计数排序的数组范围太大,数组就会太大,所以基数排序就是以多次的计数排序搞定排序。根据上面的图示很清楚的看到两位数经过十位和个位数的两次排序,完成排序。
#include <iostream>
using namespace std;
/*
* 打印数组
*/
void printArray(int array[],int length)
{
for (int i = 0; i < length; ++i)
{
cout << array[i] << " ";
}
cout << endl;
}
/*
*求数据的最大位数,决定排序次数
*/
int maxbit(int data[], int n)
{
int d = 1; //保存最大的位数
int p = 10;
for(int i = 0; i < n; ++i)
{
while(data[i] >= p)
{
p *= 10;
++d;
}
}
return d;
}
void radixsort(int data[], int n) //基数排序
{
int d = maxbit(data, n);
int tmp[n];
int count[10]; //计数器
int i, j, k;
int radix = 1;
for(i = 1; i <= d; i++) //进行d次排序
{
for(j = 0; j < 10; j++)
count[j] = 0; //每次分配前清空计数器
for(j = 0; j < n; j++)
{
k = (data[j] / radix) % 10; //统计每个桶中的记录数
count[k]++;
}
for(j = 1; j < 10; j++)
count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
{
k = (data[j] / radix) % 10;
tmp[count[k] - 1] = data[j];
count[k]--;
}
for(j = 0; j < n; j++) //将临时数组的内容复制到data中
data[j] = tmp[j];
radix = radix * 10;
}
}
int main()
{
int array[10] = {73,22,93,43,55,14,28,65,39,81};
radixsort(array,10);
printArray(array,10);
return 0;
}
参考文章
https://www.cnblogs.com/chengxiao/p/6129630.html
https://blog.csdn.net/weixin_42109012/article/details/91668543
https://blog.csdn.net/qq_28584889/article/details/88136498