排序算法的学习

八大排序

一、内部排序

1.1 插入排序

1.1.1 直接插入排序

cbb689e39d3fa679802ef7b5031d980d.gif

直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:

  1. 第一层循环:遍历待比较的所有数组元素
  2. 第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
    如果:selected > ordered,那么将二者交换

假设排序顺序从左至右,具体步骤如下:

  1. 列表第一个元素和前面元素比较,如果小于前面元素(其实不存在),则交换位置。(这步其实可以没有)

  2. 列表第二个元素和前面元素(第一个元素)比较,如果小于前面元素,则交换位置。

  3. 列表第三个元素和前面元素(第二个元素)比较,如果小于前面元素,则交换位置。如果和前面元素交换了位置,现在在第二个位置上,则接着继续和前面元素比较(第一个元素),如果小于前面元素,接着再次交换位置,然后再次重复比较过程…

…继续重复以上过程,直到最后一个元素完成比较

比较移动过程中,如果元素不需要移动意味着该元素排序完毕。
#include <stdio.h>
#include <string.h>  //能够使用strlen()函数

char a[10] = { 8,4,15,2,5,10,14,6,8,18 };

int main(void)
{
	int temp = 0;
	int len = strlen(a);  //取得数组长度
	
	for (int i = 1; i < len; i++)   //遍历待比较的所有数组元素
	{
//将本轮元素与前面已经排好序的元素进行比较,如果前面的元素大于后面的元素,则一直交换,否则结束此次for循环
		for (int j = i-1; j >= 0 && a[j] > a[j + 1]; j--) 
		{
			temp = a[j];
			a[j] = a[j + 1];
			a[j + 1] = temp;
		}
	}
	
	for (int i = 0; i < 10; i++) //打印数组
	{
		printf("%d ", a[i]);
	}
	getchar();//执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
	return 0;
}
1.1.2 希尔排序

img

希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
同样的:从上面的描述中我们可以发现:希尔排序的总体实现应该由三个循环完成:

  1. 第一层循环:将gap依次折半,对序列进行分组,直到gap=1
  2. 第二、三层循环:也即直接插入排序所需要的两次循环。具体描述见上。
#include <stdio.h>
#include <string.h>  //能够使用strlen()函数

char a[10] = { 8,4,15,2,5,10,14,6,8,18 };

int main(void)
{
	int len = strlen(a);  //取得数组长度
	int increment = len;
	int temp = 0;
	int j=0;
	while (increment > 1)
	{
		increment = increment / 3 + 1; //增量的取法之一:除三向下取整+1
		for (int i = increment; i < len; i++)   //进行(len - increment)轮
		{
			if (a[i- increment] > a[i])  //前面的数和后面的数比较比较
			{
				temp = a[i];  //如果前面的数比较大,则把后面的数存入temp中
				j = i - increment;  //j代表前面的数
				while (j >= 0 && a[j] > temp)
				{
					//如果前面的数比后面的大,则后面的数等于前面的数
					a[j + increment] = a[j];  
					//如果出现a[1]和a[5]比完,a[5]与a[9]比,如果a[5]>a[9],则a[1]再和a[9]比
					j = j - increment;  
				}
	//如果a[1]>a[9],则a[5]不变,a[1]与a[9]互换;如果a[1]<a[9]&&a[5]>a[9],则a[5]与a[9]互换
				a[j + increment] = temp; 
			}
		}
	}

	for (int i = 0; i < 10; i++) //打印数组
	{
		printf("%d ", a[i]);
	}
	getchar();//执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
	return 0;
}

1.2 选择排序

1.2.1 简单选择排序

img

简单选择排序的基本思想:比较+交换

  1. 从待排序序列中,找到关键字最小的元素;
  2. 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
  3. 从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
    因此我们可以发现,简单选择排序也是通过两层循环实现。
    第一层循环:依次遍历序列当中的每一个元素
    第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
#include <stdio.h>
#include <string.h>  //能够使用strlen()函数

char a[10] = { 8,4,15,2,5,10,14,6,8,18 };

int main(void)
{
	int dat = 0;  //用来保存每次遍历最小值的数组下标
	int temp = 0;
	int len = strlen(a);  //取得数组长度
	for (int i = 0; i < len-1; i++) //遍历整个数组(最后一个不用)
	{
		dat = i;
		for (int j = i + 1;j < len; j++)  //从i以后遍历数组与i比较
		{
			if (a[j] < a[dat])  //找出最小的值,把该值的数组下标给dat
			{
				dat = j;
			}
		}
		temp = a[i];  //dat的值与i的值的位置进行交换,放到已排序列表后面
		a[i] = a[dat];
		a[dat] = temp;
	}

	for (int i = 0; i < 10; i++) //打印数组
	{
		printf("%d ", a[i]);
	}
	getchar();//执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
	return 0;
}
1.2.2 堆排序

堆排序的实现:

  • 堆排序是利用堆的性质进行的一种选择排序。

其基本思想为(大顶堆):

  1. 假设一个初始待排序的长度为n;

  2. 将初始待排序关键字序列(R0,R1,R2,…,Rn-1)构建成大顶堆,此堆为初始的无序区;

  3. 将堆顶元素R[0]与最后一个元素R[n-1]交换,此时得到新的无序区(R0,R2,…,Rn-2)和新的有序区(Rn-1),且满足R[1,2,…,n-2]<=R[n-1];

  4. 由于交换后新的堆顶R[0]可能违反堆的性质,因此需要对当前无序区(R0,R1,…,Rn-2)调整为新堆,然后再次将R[0]与无序区最后一个元素交换,得到新的无序区(R0,R1,R2,…,Rn-3)和新的有序区(Rn-2,Rn-1)。

  5. 不断重复此过程直到有序区的元素个数为n-2,则整个排序过程完成。

#include <stdio.h>
#include <string.h>  //能够使用strlen()函数
//#include <stdlib.h>


void HeapAdjust(char array[], int i, int len) 
{
    int MaxChild;  //用来存放最大子节点
    int Parent;  //用来存放父节点的值
    int LeftChild  = 2 * i + 1;  //假设父节点为i,则其左子节点为2i+1
    int RightChild = 2 * i + 1;  //假设父节点为i, 则右子节点为2i + 2

    for (Parent = array[i]; 2 * i + 1 < len; i = MaxChild)  //每次将最大子节点作为父节点向下判断
    {
        
        LeftChild = 2 * i + 1;  
        RightChild = 2 * i + 2; 
        MaxChild = LeftChild;  //先假设左节点为最大值
        if (MaxChild != len- 1 && array[RightChild] > array[LeftChild])  //先判断该节点有没有超过超度,再判断左右子节点哪个大,  找到值最大的孩子结点
        {
                MaxChild = RightChild;  //如果右节点大,则进来,把MaxChild变为右子节点
        }
    
        if (Parent < array[MaxChild])  // 如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
        {
            array[i] = array[MaxChild];
        }
        else  // 否则退出循环
        {
            break;
        }
    }
    array[i] = Parent;  // 最后把原本的父节点放到合适的位置

}


void HeapSort(char array[], int length)
{
    //构建堆的过程    
    for (int i = (length / 2 - 1); i >= 0; i--)  // 调整序列的前半部分元素,(即每个有孩子的节点)调整完之后是一个大顶堆,第一个元素是序列的最大的元素,从下往上判断
    {
        HeapAdjust(array, i, length);
    }


    for (int i = length - 1; i > 0; i--)  //通过不断地筛选出最大值,同时不断地进行筛选剩余元素
    {
        int temp;
    
        temp = array[i];      // 把第一个元素和当前的最后一个元素交换,保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
        array[i] = array[0];
        array[0] = temp;   //array[i]为最大值,arrat[i-1]为第二大,arrat[i-2]为第三大,以此类推


        HeapAdjust(array, 0, i);   // 不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
    }

}

int main()
{
    int len;
    char arr_test[17] = {15, 8, 12, 4, 2, 3, 5, 1, 6, 9, 0, 7, 10, 11, 14, 15, 1 };  //测试数据

    len = sizeof(arr_test);  //计算数组长度
    HeapSort(arr_test, len);  //堆排序算法


    for (int i = 0; i < len; i++)  //将排序好的数据打印出来
    {
        printf("%d  ", arr_test[i]);
    }   
    getchar();  //执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
    return 0;

}

1.3 交换排序

1.3.1 冒泡排序

冒泡排序是比较基础的排序算法之一,其思想是相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。

img

冒泡排序思路比较简单:

  1. 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;( 第一轮结束后,序列最后一个元素一定是当前序列的最大值)
  2. 对序列当中剩下的n-1个元素再次执行步骤1;
  3. 对于长度为n的序列,一共需要执行n-1轮比较;(利用while循环可以减少执行次数)
#include <stdio.h>
#include <string.h>  //能够使用strlen()函数

int main()
{
    char a[10] = { 12 ,43,9,13,67,98,101,89,3,35 };//十个数的无序数列
    int temp,len;
    len = sizeof(a);
    
    //冒泡排序
    for (int i = 0; i < len-1 ; i++)//n个数的数列总共扫描n-1次,每次把最大的数排到后面,然后再拍前面没排过的
    {
        for (int j = 0; j < len - i -1; j++)//每一趟扫描到a[n-i-2]与a[n-i-1]比较为止结束
        {
            if (a[j] > a[j + 1])//后一位数比前一位数小的话,就交换两个数的位置(升序)
            {
                temp = a[j + 1];
                a[j + 1] = a[j];
                a[j] = temp;
            }
        }
    }
     
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", a[i]);
    }
    getchar();  //执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
    return 0;
}
1.3.1 快速排序

img

快速排序的基本思想:挖坑填数+分治法

  1. 先从数列中取出一个数作为基准数;(在这里选择序列当中第一个数最为基准数)
  2. 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边;
  3. 再对左右区间重复第第一步、二步,直到各区间只有一个数;

用伪代码描述如下:

  1. i =L; j = R; 将基准数挖出形成第一个坑a[i]。
  2. j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
  3. i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中
  4. 再重复执行2,3二步,直到i==j,将基准数填入a[i]中
  5. 然后再用以上同样的方法排序K左边的数,和K右边的数
#include <stdio.h>
#include <string.h>  //能够使用strlen()函数


void QuickSort(char* arr, int low, int high)
{
    if (low < high)
    {
        int i = low;
        int j = high;
        int k = arr[low];
        while (i < j)
        {
            while (i < j && arr[j] >= k)     // 从右向左找第一个小于k的数
            {
                j--;  //如果找不到,就往前一格继续找
            }

            if (i < j)  //把找出来的小于K的数,放在前面,第一个小于K的数放在第一个,第二个小于K的数放在第二个,以此类推
            {
                arr[i] = arr[j];  //把找出来的最小的数放到a[i]的位置
                i++;  //i往后移一格
            }
    
            while (i < j && arr[i] < k)      // 从左向右找第一个大于等于k的数
            {
                i++;  //如果找不到,就往后一格继续找
            }
    
            if (i < j)  //把找出来的大于K的数,放在后面,第一个大于K的数放在最后一个,第二个大于K的数放在倒数第二个,以此类推
            {
                arr[j] = arr[i];  //把找出来的最大的数放到a[j]的位置
                j--;  //j往后移一格
            }
        }
        //到此为止,小于K的值都在K左边;大于K的值都在K右边
    
        arr[i] = k; //将基准值放入剩下的那个坑里面
    
        // 递归调用
        QuickSort(arr, low, i - 1);     // 排序k左边的数
        QuickSort(arr, i + 1, high);    // 排序k右边的数
    }

}
// 主函数
int main()
{
    int len;
    char array[10] = { 12,85,25,16,34,23,49,95,17,61 };
    len = sizeof(array);
    

    QuickSort(array, 0, len - 1);  // 快速排序
    for (int i = 0; i < len; i++)  //打印出来
    {
        printf("%d ", array[i]);
    }
    getchar();  //执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
    return 0;
}

1.4 归并排序

img

img

  1. 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个典型的应用。它的基本操作是:将已有的子序列合并,达到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并, 使用中牺牲空间换取时间的算法
  2. 归并排序其实要做两件事:
  • 拆分----将序列每次折半拆分;
  • 合并----将划分后的序列段两两排序合并;

​ 因此,归并排序实际上就是两个操作,拆分+合并

拆分:
img

合并:
img

  1. 如何分解?
    在这里,我采用递归的方法,首先将待排序列分成A,B两组;然后重复对A、B序列分组;直到分组后组内只有一个元素,此时我们认为组内所有元素有序,则分组结束;
  2. 如何合并?
    arr[first,…,mid]为第一段,arr[mid+1,…,last]为第二段,并且两端已经有序,现在我们要将两端合成达到temp[first…last]并且也有序:
  • 首先依次从第一段与第二段中取出元素比较,将较小的元素赋值给temp[];
  • 重复执行上一步,当某一段赋值结束,则将另一段剩下的元素赋值给temp[];
  • 此时将temp[]中的元素复制给arr[],则得到的arr[first…last]有序;

img

  1. 代码解析
#include<stdio.h>
#include<string.h>

#define ArrLen 20

void merge(char arr[], int start, int mid, int end)  //对区间进行排序
{
	int result[ArrLen];  //用来进行两个区间的合并
	int temp = 0;  //用来存储合并以后有序的序列
	int i = start;  //第一个区间的开始
	int j = mid + 1;  //第二个区间的开始
	while (i <= mid && j <= end)  //把区间分成[start,mid]和[mid+1,end]进行排序
	{
		if (arr[i] < arr[j])  //比较两个区间的第一个元素谁小,如果区间一的第一个元素较小,则放进result的第一位,然后区间一的第二个元素和区间二的第一个元素比较,谁小就放入result的下一位,以此类推,直到其中一个区间遍历完
		{
			result[temp++] = arr[i++];
		}
		else  //如果区间二的元素较小,则放进result,以此类推
		{
			result[temp++] = arr[j++];
		}
	}

	if (i == mid + 1)  //如果第一个区已经遍历完了
	{
		while (j <= end)  //并且第二个区还没有遍历完
		result[temp++] = arr[j++];  //将第二个区间的值按顺序排在result后面
	}
	if (j == end + 1)  //如果第二个区已经遍历完了
	{
		while (i <= mid)  //并且第一个区还没有遍历完
		result[temp++] = arr[i++];  //将第一个区间的值按顺序排在result后面
	}
	for (j = 0, i = start; j < temp; i++, j++)  //把排序好的数组result[]放入arr[]数组中
	{
		arr[i] = result[j];
	}
}

void mergeSort(char arr[], int start, int end)
{
	if (start >= end)  //如果区间只剩下一个值,则停止递归,返回0
	{
		return;
	}
	int mid = (start + end) / 2;   //取中间值
	mergeSort(arr, start, mid);    //用中间值把去区间分成[start,min]和[min+1,end]
	mergeSort(arr, mid + 1, end);  //用中间值把去区间分成[start,min]和[min+1,end]
	merge(arr, start, mid, end);   //每次把分出来区间进行排序
}

int main()
{
	int len;
	char arr[] = { 4, 7, 6, 5, 2, 1, 8, 2, 9, 1 };
	len = sizeof(arr);

	mergeSort(arr, 0, len-1);

	for (int i = 0; i < 10; i++)  //打印出来
	{
		printf("%d ", arr[i]);
	}

	getchar();  //执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果
//	system("pause");
	return 0;
}

1.5 基数排序

基数排序(以整形为例),

  • 将整形10进制按每位拆分,然后从低位到高位依次比较各个位。每次比较完进行排序,直到整个数组有序
    主要分为两个过程:
  1. 分配: 先从个位开始,根据位值(0-9)分别放到0~9号桶中(比如:53,个位为3,则放入3号桶中;187,个位为7,则放入7号桶中)
  2. 收集: 再将放置在0~9号桶中的数据按顺序放到数组中
  3. 重复(1)(2)过程,从个位到最高位,直到排好序为止(比如32位无符号整形最大数4294967296,最高位为10位)

打个比方:

  • 假设有欲排数据序列:73 22 93 43 55 14 28 65 39 81

  • 首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一一对应)中。

分配结果(逻辑想象)如下图所示:

img

  • 分配结束后,接下来将所有桶中所盛数据按照桶号由小到大(桶中由顶至底)依次重新收集串起来

  • 得到如下仍然无序的数据序列:81 22 73 93 43 14 55 65 28 39

  • 接着,再进行一次分配,这次根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图所示:

img

  • 分配结束后,接下来再将所有桶中所盛的数据(原理同上)依次重新收集串接起来

  • 得到如下的数据序列:14 22 28 39 43 55 65 73 81 93

  • 观察可以看到,此时原无序数据序列已经排序完毕

  • 如果排序的数据序列有三位数以上的数据,则重复进行以上的动作直至最高位数为止

#include<stdio.h>
#include<stdlib.h>

#define Len 10      //数组个数
#define RADIX_10 10    //整形排序,共有有10个桶分别对应着0-9
#define KeyNum 10     //关键字个数,这里为整形位数,最高可以筛选到10个位(个位、十位、百位、千位......)


// 找到num的从低到高的第pos位的数据
int GetNumInPos(int num, int pos)
{
	int temp = 1;
	for (int i = 0; i < pos - 1; i++)//筛选出num数组中的个位或十位或百位......
	{
		temp *= 10;
	}
	return (num / temp) % 10;  //返回找出来的那个位
}


void RadixSort(int* arr, int arr_Len)
{
	int* temp[RADIX_10];    //分别为0~9的序列空间,通俗来说就是有10个桶
	int dat;  //用来存放temp[num]中存了多少个数
	int num;  //用来存放通过函数GetNumInPos()返回来的那个值

	for (int i = 0; i < 10; i++)  //为temp分配空间
	{
		temp[i] = (int*)malloc(sizeof(int) * (arr_Len + 1));  //为数组temp分配空间
		temp[i][0] = 0;    //每个temp[i][0]都是用来记录该temp[i]中放入了多少个数据
	}
	
	for (int pos = 1; pos <= KeyNum; pos++)    //从个位开始,总共循环10个位
	{
		for (int i = 0; i < arr_Len; i++)    //分配过程,把arr[]中的每个数都分配一次
		{
			num = GetNumInPos(arr[i], pos);  //得到arr[i]中的pos位
			temp[num][0]++;  //temp[num]中加了一个数,所以temp[num]的第0位加1
			dat = temp[num][0];  //把temp[num]中存了多少个数告诉dat
			temp[num][dat] = arr[i];  //将数据放入temp[num]的第dat位
		}
	
		for (int i = 0, j = 0; i < 10; i++)    //收集
		{
			for (int k = 1; k <= temp[i][0]; k++)  //把分配好的按顺序返回arr[]数组中
			{
				arr[j] = temp[i][k];  
				j++;
			}
			temp[i][0] = 0;    //并将用来记录个数的位给复位
		}
	}

}

int main()
{
	int arr_test[Len] = { 854, 424, 227, 38, 524, 124, 635, 922, 140, 75 };

	RadixSort(arr_test, Len);  //基数排序
	
	for (int i = 0; i < Len; i++)  //打印出来
	{ 
		printf("%d  ", arr_test[i]);
	}
	
	getchar();  //执行执行完毕后不立即返回代码窗口,等待回车后再返回,便于查看运行结果。
	return 0;
}

动态分配玩内存要用释放~ 不然可能会因此丢了工作

1.6 总结

屏幕截图 2021-08-17 132507

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值