排序整理(c++实现),搭配图解

十大排序算法

img
image
稳定:如果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上面。

然后对两个哨兵进行交换
img
img
第一次交换结束

然后在按照刚刚的顺序,j在向左寻找,找到4比6小,i向右寻找,找到9比6大,之后交换
img
第二次结束

j在向左,找到3比6小,但是当i移动的时候两个哨兵相遇了,所以本次循环停止。停在3上面。最后在把3和基准数6交换之后完成。
img
以6为分界线,对左右边再次调用快速排序进行递归,算法完成
img

代码:

从小到大
//快速排序(从小到大)
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.冒泡排序

image

方法

冒泡其实很好理解

冒泡就是把最大的东西放在后面(小到大)或把最小的放在后面(大到小)

方法就是在一次循环中遍历找到最大的数放到后面,找到一个比前面大的数就进行交换,

在从剩下的数中在重发刚刚的循环。完成

代码:

//冒泡排序
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.选择排序

image

方法:

选择排序和冒泡的区别就是,他是最后交换,所以比冒泡做了优化,减少了交换次数。

方法就是用一个数做基准数,在一次循环中找到一个最大或最小的数,然后与最左或最右的数进行交换。

每次循环用相同方法。完成

代码

//选择排序
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.插入排序

img

方法

我的方法就是把要排列的数组看成两个数组

排序好的,和没有加入排序的。

每次从没有排序好的那一部分中取出一个数,和排序好的做比较,不符合要求就把数字向后移动一位留出空间,符合要求就进行插入操作。

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.归并排序

方法

先把一个数组的所有的数字分成一半,一半,再一半,最后临时开辟的空间中排序合并。

img
img

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、最小堆(小顶堆):堆的每个父节点都小于其孩子节点;
image
堆的存储:
一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如下图所示:
image

实现图解

在这里插入图片描述

// 从小到大排序
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); // 重新建立大顶堆

    }
}

桶排序

其实桶排序也有插入排序的意思,他只是把插入排序变成分组的罢了。
image

原理

假如有途中这么几个数要排序。我们假定要分成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
image
遍历数组,如果遍历到4,就在下标为4的数字+1,记录数字的个数
image
最后按照数组个数,直接输出排序后的数组。

#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;
}

基数排序

原理

基数排序其实跟计数排序差不多,只要理解了计数排序,基数排序就其实一样
image
首先计数排序的数组范围太大,数组就会太大,所以基数排序就是以多次的计数排序搞定排序。根据上面的图示很清楚的看到两位数经过十位和个位数的两次排序,完成排序。

#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

posted @ 2021-03-28 22:36  爱吃鱼的小女孩  阅读(183)  评论(0编辑  收藏  举报