十种常用的排序算法---C语言版本

目录

0 排序算法的简介

1. 冒泡排序

2. 选择排序

4. 希尔排序

5. 归并排序

6. 快速排序

7. 堆排序

8. 计数排序

9. 桶排序

10. 基数排序


0 排序算法的简介

0.1 排序的定义
      对一序列对象根据某个关键字进行排序。
0.2 术语说明
     稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
    不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
    内排序 :所有排序操作都在内存中完成;
    外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
    时间复杂度 : 一个算法执行所耗费的时间。
    空间复杂度 :运行完一个程序所需内存的大小。
0.3 算法总结

image

    n: 数据规模;
    k: “桶”的个数;
    In-place: 占用常数内存,不占用额外内存;
    Out-place: 占用额外内存;

04 算法分类

image

0.5 比较和非比较的区别

    常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序 。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置 。
    在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。
    比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
    计数排序、基数排序、桶排序则属于非比较排序 。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置 。
    非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。
    非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

1. 冒泡排序

    冒泡排序 是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
1.1 算法描述
    步骤1: 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
    步骤2: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
    步骤3: 针对所有的元素重复以上的步骤,除了最后一个;
    步骤4: 重复步骤1~3,直到排序完成。
1.2 动图演示

image
1.3 代码实现

#include 

/*https://www.cnblogs.com/jyroy/p/11248691.html*/
void swap(int *a, int *b)
{
	int temp;
	temp = *a;
	*a 	 = *b;
	*b   = temp;
}

void swap1(int *a, int *b)
{
	*a = *a ^ *b;
	*b = *a ^ *b;
	*a = *a ^ *b;
}
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

void popSort(int *src ,int length)
{
	int i,j;
	int temp;	

	if(src == NULL || length <= 0)
		return;

	for(i = 0; i < length-1; i++)
	{
		for(j = 0; j < length-1-i;j++)
		{
			if(src[j] > src[j+1])
			{
				swap(&src[j],&src[j+1]);
			}			
		}

		printf("%d: ",i+1);
		printArray(src,length);
	}
}

void popSort1(int *src ,int length)
{
	int i,j;
	int temp;
	int flag = 0;
	if(src == NULL || length <= 0)
		return;

	for(i = 0; i < length-1; i++)
	{
		flag = 1;
		for(j = 0; j < length-1-i;j++)
		{
			if(src[j] > src[j+1])
			{
				swap(&src[j],&src[j+1]);
				flag = 0;
			}
		}

		if(flag == 1)
		{		
			return;
		}

		printf("%d: ",i+1);
		printArray(src,length);
	}
}


void popSort2(int *src ,int length)
{
	int i,j;
	int temp;
	int len = length - 1;
	int flag = 0;
	int tempPosition = 0;

	if(src == NULL || length <= 0)
		return;
	for(i = 0; i < length-1; i++)
	{
		flag = 1;
		for(j = 0; j < len;j++)
		{
			if(src[j] > src[j+1])
			{
				swap(&src[j],&src[j+1]);
				flag = 0;
				tempPosition = j;
			}			
		}

		len = tempPosition;
		if(flag == 1)
		{
			return;
		}

		printf("%d: ",i+1);
		printArray(src,length);
	}
}

int main(void)
{
	int src[7] = {6,4,7,5,1,3,2};
	int src1[7] = {6,4,7,5,1,3,2};
	int src2[7] = {6,4,7,5,1,3,2};
	popSort(src,7);	
	printf("---------------\n");
	popSort1(src1,7);
	printf("---------------\n");
	popSort2(src2,7);
	return 0;
}

2. 选择排序

     选择排序 是表现最稳定的排序算法之一 ,因为无论什么数据进去都是O(n2)的时间复杂度 ,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
    选择排序(Selection-sort) 是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
2.1 算法描述
    n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
    步骤1:初始状态:无序区为R[1…n],有序区为空;
    步骤2:第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序   从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
步骤3:n-1趟结束,数组有序化了。
2.2 动图演示

image
2.3 代码实现 

#include 
/* https://www.cnblogs.com/onepixel/articles/7674659.html */
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

void selectSort(int *array, int length)
{
	int i,j;
	int index;
	int min;
	
	if(array == NULL || length <= 0)
		return;

	for(i = 0;i < length;i++)
	{
		min = array[i];
		index = i;
		for(j = i+1;j < length;j++)
		{
			if(array[j] < min)
			{
				min = array[j];
				index = j;
			}
		}
		
		array[index] = array[i];
		array[i]	 = min;

		
		printf("%d: ",i+1);
		printArray(array,length);
	}
}

int main(void)
{
	//int src[7] = {6,4,7,5,1,3,2};
	int src[7] = {1,2,3,4,7,6,5};
	selectSort(src,7);
	printf("---------------\n");
	printArray(src,7);
}

3. 插入排序

      插入排序(Insertion-Sort) 的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
3.1 算法描述
     一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
     步骤1: 从第一个元素开始,该元素可以认为已经被排序; 
     步骤2: 取出下一个元素,在已经排序的元素序列中从后向前扫描;
     步骤3: 如果该元素(已排序)大于新元素,将该元素移到下一位置;
     步骤4: 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
     步骤5: 将新元素插入到该位置后;
     步骤6: 重复步骤2~5。
3.2 动图演示

image

3.3 代码实现

#include 
/* https://www.cnblogs.com/onepixel/articles/7674659.html */
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

void insertSort(int *array, int length)
{
	int i;
	int current; 	/* 需要排序的数字 */
	int preIndex;	/* 已经排序的索引 */

	if(array == NULL || length <= 0)
		return;

	for(i = 1; i < length;i++)
	{
		preIndex = i-1;
		current  = array[i];

		/* 找到插入位置 */
		while(preIndex >= 0 && array[preIndex] > current)
		{
			array[preIndex + 1] = array[preIndex];
			preIndex--;
		}

		array[preIndex + 1] = current;
	}
}

int main(void)
{
	int src[7] = {6,4,7,5,1,3,2};
	//int src[7] = {1,2,3,4,7,6,5};
	insertSort(src,7);
	printf("---------------\n");
	printArray(src,7);

	return 0;
}

4. 希尔排序

    希尔排序是希尔(Donald Shell) 于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
    希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
4.1 算法描述
     我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
     先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
    步骤1:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1; 
    步骤2:按增量序列个数k,对序列进行k 趟排序;
    步骤3:每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
4.2 过程演示

image
4.3 算法实现

#include 
/* https://www.cnblogs.com/itsharehome/p/11058010.html */
/* https://blog.csdn.net/weixin_41190227/article/details/86600821 */
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

static void insertI(int *array,int h, int i)
{	
	if(array == NULL)
		return;
	int temp = array[i];
	int k;
	for(k = i - h; k >= 0 && temp < array[k]; k -= h)
	{
		array[k + h] = array[k];
	}

	array[k + h] = temp;
}

void shellSort(int *array, int length)
{
	if(array == NULL || length <= 0)
		return;
	int h = 0;
	int i;
	for(h = length/2; h > 0; h /= 2)
	{	
		/* 对各个局部分组进行插入排序 */
		for(i = h; i < length;i++)
		{
			/* 将arr[i]插入到所在分组的正确位置上 */
			insertI(array,h,i);
		}
	}
	
}

int main(void)
{
	int src[7] = {6,4,7,5,1,3,2};
	shellSort(src,7);
	printf("---------------\n");
	printArray(src,7);
}

5. 归并排序

    和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
    归并排序 是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
5.1 算法描述
    步骤1:把长度为n的输入序列分成两个长度为n/2的子序列;
    步骤2:对这两个子序列分别采用归并排序; 
    步骤3:将两个排序好的子序列合并成一个最终的排序序列。
5.2 动图演示

image
5.3 算法实现

#include 
#include 
#include 

/* https://blog.csdn.net/ds19980228/article/details/82863531 */
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

void _merge(int *array, int begin1,int end1,int begin2,int end2,int *tmp)
{
	int index = begin1;
	int i = begin1,j = begin2;
	/* 注意:当划分的区间足够小时,begin1==end1,begin2==end2 */
	while(i <= end1 && j <= end2)
	{
		if(array[i] <= array[j])
		{
			tmp[index++] = array[i++];
		}
		else
		{
			tmp[index++] = array[j++];
		}
	}

	/* 将左边元素填充到tmp中 */
	while(i <= end1)
	{
		tmp[index++] = array[i++];
	}
	
	/* 将右边元素填充到tmp中 */
	while(j <= end2)
	{
		tmp[index++] = array[j++];
	}

	/* 将tmp中的数据拷贝到原数组对应的序列区间, 注意:end2-begin1+1 */
	memcpy(array + begin1,tmp + begin1, sizeof(int)*(end2 - begin1 + 1));

	printf("---end1 = %d: ",end1);
	printArray(array,7);

	printf("-------------\n");
}

void mergeSort(int *array, int left, int right, int *tmp)
{
	if(array == NULL || tmp == NULL)
		return;
	if(left >= right)
		return;
	/* mid将数组一分为二 */
	int mid = left + ((right - left) >> 1);
	/* 左边归并排序,得到左子序列有序 */
	mergeSort(array,left,mid,tmp);
	/* 右边归并排序,得到右子序列有序 */	
	mergeSort(array,mid+1,right,tmp);
	/* 将两个有序子数组合并 */
	_merge(array,left,mid,mid+1,right,tmp);
}

int main(void)
{
	int src[7] = {6,4,7,5,1,3,2};	
	int *tmp = (int *)malloc(sizeof(int)*(sizeof(src) / sizeof(int)));
	if(tmp == NULL)
		return -1;
	
	mergeSort(src,0,sizeof(src) / sizeof(int) - 1,tmp);
	printf("---------------\n");
	printArray(src,7);
	free(tmp);
}

6. 快速排序

     快速排序 的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
6.1 算法描述
     快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
     步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
     步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
     步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
6.2 动图演示

image
6.3 代码实现

#include 
#include 
#include 

/* http://emb.hqyj.com/Column/Column309.htm */
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

static int _partition(int *array,int low,int high)
{
	int base = 0;
	base = array[low];
	while(low < high)
	{
		while(low < high && array[high] >= base)
			high--;
		array[low] = array[high];

		while(low < high && array[low] <= base)
			low++;
		array[high] = array[low];
	}

	array[low] = base;
	return low;
}

void quickSort(int *array,int low,int high)
{
	if(low >= high)
		return;
	int pivoloc = _partition(array,low,high);

	quickSort(array,low,pivoloc - 1);
	quickSort(array,pivoloc + 1,high);
}

int main(void)
{
	int src[7] = {6,4,7,5,1,3,2};	
	
	quickSort(src,0,7-1);
	printf("---------------\n");
	printArray(src,7);
}

7. 堆排序

    堆排序(Heapsort) 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
7.1 算法描述
     步骤1:将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
     步骤2:将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
     步骤3:由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
7.2 动图演示


7.3 代码实现

#include 

/* https://www.cnblogs.com/lanhaicode/p/10546257.html */


/* Function: 交换交换根节点和数组末尾元素的值*/
void Swap(int *heap, int len)
{
    int temp;
	if(heap == NULL || len <= 0)
		return;

    temp = heap[0];
    heap[0] = heap[len-1];
    heap[len-1] = temp;
}

/* Function: 构建大顶堆 */
void BuildMaxHeap(int *heap, int len)
{
    int i;
    int temp;
	if(heap == NULL || len <= 0)
		return;

    for (i = len/2-1; i >= 0; i--)
    {
        if ((2*i+1) < len && heap[i] < heap[2*i+1])    /* 根节点大于左子树 */
        {
            temp = heap[i];
            heap[i] = heap[2*i+1];
            heap[2*i+1] = temp;
            /* 检查交换后的左子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
            if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
            {
                BuildMaxHeap(heap, len);
            }
        }

        if ((2*i+2) < len && heap[i] < heap[2*i+2])    /* 根节点大于右子树 */
        {
            temp = heap[i];
            heap[i] = heap[2*i+2];
            heap[2*i+2] = temp;
            /* 检查交换后的右子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
            if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
            {
                BuildMaxHeap(heap, len);
            }
        }
    }
}

int main()
{
    int a[6] = {7, 3, 8, 5, 1, 2};
    int len = 6;    /* 数组长度 */
    int i;

    for (i = len; i > 0; i--)
    {
        BuildMaxHeap(a,i);
        Swap(a,i);
    }
	
    for (i = 0; i < len; i++)
    {
        printf("%d ", a[i]);
    }
	printf("\n");
    return 0;
}

8. 计数排序

     计数排序 的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
    计数排序(Counting sort) 是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
8.1 算法描述
     步骤1:找出待排序的数组中最大和最小的元素;
     步骤2:统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
     步骤3:对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
     步骤4:反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
8.2 动图演示

image
8.3 代码实现

#include 
#include 

/* https://blog.csdn.net/weixin_41190227/article/details/86600821 */
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}


void countSort(int *array, int length)
{
	if(array == NULL || length <= 0)
		return;

	int i = 0,j = 0;
	int maxValue = array[0];
	int minValue = array[0];

	/*Step 1. 找到最大值和最小值 */
	for(i = 0; i < length;i++)
	{
		if(array[i] > maxValue)
		{
			maxValue = array[i];
		}
		if(array[i] < minValue)
		{
			minValue = array[i];
		}
	}

	/*Step 2. 申请临时数组内存*/
	int range = maxValue - minValue + 1;
	int *tmp = (int *)calloc(range,sizeof(int));
	
	/*Step 3. 使用临时数组记录原始数组中每个数的个数 */
	for(i = 0; i < length; i++)
	{
		/* 注意:这里在存储上要在原始数组数值上减去min才不会出现越界问题 */
		tmp[array[i] - minValue]++;
	}

	/* Step 4. 对原始数组重新排序 */
	for(i = 0; i < range;i++)
	{
		while(tmp[i]--)
		{
			/* 注意:要将i的值加上min才能还原到原始数据 */
			array[j++] = i + minValue;
		}
	}

	free(tmp);
}

int main(void)
{
	int src[] = { 3, 4, 3, 2, 1, 2, 6, 5, 4, 7 };
	
	countSort(src,sizeof(src)/sizeof(int));	
	printArray(src,sizeof(src)/sizeof(int));
	printf("---------------\n");
	return 0;
}

9. 桶排序

    桶排序 是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
    桶排序 (Bucket sort)的工作的原理:
假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排
9.1 算法描述
     步骤1:人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
     步骤2:遍历输入数据,并且把数据一个一个放到对应的桶里去;
     步骤3:对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
     步骤4:从不是空的桶里把排好序的数据拼接起来。 

     注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。
9.2 图片演示

image
9.3 代码实现

#include 

// 桶排序
// 用映射关系替代两数之间的比较。

// 这里讨论小范围内(0-99随机数)的桶排序算法实现。
// 扩充为大范围的桶排序算法,大多数做法为链表。并且此时的桶排序,总体看来有些归并算法的意味。

// 主要思路
// 1. 实现100个桶,分别对应 0 - 99。
// 2. 每遍历到一个数,对应的桶值自增。
// 3. 最后利用桶本身的顺序,直接输出即可。


//注意,这个函数需要输入的数组值在 0 - 99范围内波动
void bucketSort(int source_array[], int source_array_length)
{
    int i, j, k;

    // 1. 创建100个桶,并初始化为 0。
    int tmp_bucket[100] = {0};

    // 2. 将桶索引视为数组的“元素”,桶索引对应的值就是数组“该元素的个数”。
    for (i = 0; i < source_array_length; i++)
    {
        // 比如, 若原数组是[4, 2, 1, 0]
        // 桶数组初始为 [0, 0, 0, 0, 0],桶的索引对应了原数组的数据区间
        // 当遍历原数组第一个元素4时,则桶数组[4] 加1,桶数组变为 [0, 0, 0, 0, 1]。用映射替代了比较,实现排序。
        tmp_bucket[source_array[i]]++;
    }

    // 遍历桶数组(桶数组长度100),改变原数组
    j = 0;
    for (i = 0; i < 100; i++)
    {
        for (k = 0; k < tmp_bucket[i]; k++)
        {
            source_array[j] = i;
            j++;
        }

    }
}

int main()
{
    // 生成随机测试列表 0-99
    int test_list[20];
    int test_list_length = sizeof(test_list) / sizeof(int);
	int i;
	
    for (i = 0; i < test_list_length; i++)
    {
        test_list[i] = rand() % 100;
        printf("%d ", test_list[i]);
    }
    printf("\n");

    // 普通桶排序
    bucketSort(test_list, test_list_length);

    for (i = 0; i < test_list_length; i++)
    {
        printf("%d ", test_list[i]);
    }
    printf("\n");

    return 0;
}

10. 基数排序

    基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;
    基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
10.1 算法描述
    步骤1:取得数组中的最大数,并取得位数;
    步骤2:arr为原始数组,从最低位开始取每个位组成radix数组;
    步骤3:对radix进行计数排序(利用计数排序适用于小范围数的特点);
10.2 动图演示

image
10.3 代码实现

#include 
#include 

/* https://blog.csdn.net/weixin_41190227/article/details/86600821 */

#define Max_ 10      //数组个数
#define RADIX_10 10    //整形排序
#define KEYNUM_31 10     //关键字个数,这里为整形位数
 
// 打印结果
void printArray(int *array,int length)
{
	int i;
	for(i = 0; i < length;i++)
	{
		printf("%d ",array[i]);
	}

	printf("\n");
}

 
// 找到num的从低到高的第pos位的数据
int GetNumInPos(int num,int pos)
{
	int temp = 1;
	int i;
	for (i = 0; i < pos - 1; i++)
		temp *= 10;
    
	return (num / temp) % 10;
}
 
 
//基数排序  pDataArray 无序数组;iDataNum为无序数据个数
void RadixSort(int* pDataArray, int iDataNum)
{
	int i, j, pos, k;
	int *radixArrays[RADIX_10];    //分别为0~9的序列空间
	for (i = 0; i < 10; i++)
	{
		radixArrays[i] = (int *)malloc(sizeof(int) * (iDataNum + 1));
		radixArrays[i][0] = 0;    //index为0处记录这组数据的个数
	}
	
	for (pos = 1; pos <= KEYNUM_31; pos++)    //从个位开始到31位
	{
		for (i = 0; i < iDataNum; i++)    //分配过程
		{
			int num = GetNumInPos(pDataArray[i], pos);
			int index = ++radixArrays[num][0];
			radixArrays[num][index] = pDataArray[i];
		}
        
		for (i = 0, j =0; i < RADIX_10; i++)    //收集
		{
			for (k = 1; k <= radixArrays[i][0]; k++)
				pDataArray[j++] = radixArrays[i][k];
			radixArrays[i][0] = 0;    //复位
		}
	}
}
 
int main()
{   //测试数据
    int arr_test[Max_] = { 28, 14, 12, 30, 51, 13, 36, 59, 0, 27 };
    //排序前数组序列
    printArray( arr_test, Max_ );
    RadixSort( arr_test, Max_);
    //排序后数组序列
    printArray( arr_test, Max_ );
    return 0;
}

 

你可能感兴趣的:(算法,快速排序,排序算法)