排序算法
本文主要介绍常用的经典排序算法
内容
重点排序算法主要分为:交换【1、2】、插入【3、4】、选择【5、6】、归并、计数排序、桶排序、基数排序 其中:1、冒泡排序
2、快速排序
3、简单插入排序
4、希尔排序
5、简单选择
6、堆排序
7、归并排序
8、计数排序
9、桶排序
10、基数排序
1 冒泡排序
这是一种简单的交换排序算法,通过比较两个元素的大小,确定是否交换顺序,通过遍历整个数列直到没有反序的记录为止。1.1 算法描述
- 比较相邻两个元素,确定是否交换
- 将数列中所有相邻元素进行比对,这样遍历一次后就能确定最后一个元素为最大值
- 重复以上步骤,但是每次都到上一次遍历的最后一个元素的前一个停止【前一次遍历排序了最大值】
1.2 时间复杂度
O(n^2)
1.3 代码实现
/*
* 算法导论 第二章 冒泡排序
* 思想:冒泡排序,顾名思义就是每一次将序列中最小的元素(较轻)移到前面去
* 轻的往上冒,重的往下掉,每次将未排序部分的最轻元素冒到最上面
* 时间复杂度为O(n^2)
*/
#include <iostream>
using namespace std;
void printArray(int arr[], int len)
{
for (int i=0; i<len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
int arr[] = {12, 21, 9, 80, 3, 11, 90, 4, 67};
int len = sizeof(arr) / sizeof(arr[0]);
cout << "原数组:" << endl;
printArray(arr, len);
for (int i=0; i<len; i++)
{
for (int j=len-1; j>i; j--)
{
if (arr[j] < arr[j-1])
{
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
cout << "冒泡排序后的数组:" << endl;
printArray(arr, len);
return 0;
}
func bubbleSort(arr []int) []int{
len := len(arr);
for i := 0; i < len - 1; i++ {
for j := 0; j < len - 1 - i; j++ {
if(arr[j] > arr[j+1] { // 相邻元素两两对比
temp := arr[j+1] // 元素交换
arr[j+1] = arr[j]
arr[j] = temp
}
}
}
return arr;
}
2 快速排序
一种交换排序算法,【分治法,D&C = Divide and Conquer的应用】通过一趟排序将数列分成两个独立的部分,一部分记录关键字均比另一部分记录的关键字小,再分别对这两部分进行排序,直到整个序列有序。2.1 算法描述
- 选取基准值------作为将序列分割为两组元素的值
- 排序将大于基准值的交换至右侧,小于在左侧,与其相同的忽略
- 递归方法【Recursion】重复上述步骤将左右子序列排序
2.2 时间复杂度
平均O(nlogn)
递归调用,平均深度为O(logn)
说明:基准值的选取对于算法的复杂度影响较大,所以该算法是不稳定的算法。
由于实现方式使用递归,针对少量数据性能不佳,不如使用直接插入排序。
优化方案:1)low-high之间随机选取 2)三数取中:选取左右中三个值,取中间值 3)优化不必要的交换。
2.3 代码实现
int Partiton(int A[], int low, int high)
{
int i = low;
int x = A[low];//用子表的第一个记录作为枢纽值
//从左往右扫描,围绕x划分数组
for(int j = low+1 ; j <= high ; j++)
{
if(A[j] <= x)
{
i++;//i表示最后一个小于等于x的索引,最后要与A[low]交换,A[0]只作为temp临时值
if(i != j)
{
A[0] = A[i];
A[i] = A[j];
A[j] = A[0];
}
}
}
//A[low]填入i位置
A[0] = A[low];
A[low] = A[i];
A[i] = A[0];
return i;
}
void quicksort(int A[], int low, int high)
{
int w;
if(low < high)
{
w = Partiton(A, low, high);//把数组划分成两部分,w为计算出的枢纽值,且返回后数组元素A[w左边]比A[w]小,右边比A[w]大
quicksort(A, low, w-1);//对左半部分递归处理
quicksort(A, w+1, high);//对有半部分递归处理
}
}
void QuickSort(int L[]) {
QSort(L,1,sizeof(L)/sizeof(L[0]))
}
package main
import "fmt"
func quickSort(arr []int, start, end int) {
if start < end {
i, j := start, end
key := arr[(start+end)/2]
for i <= j {
for arr[i] < key {
i++
}
for arr[j] > key {
j--
}
if i <= j {
arr[i], arr[j] = arr[j], arr[i]
i++
j--
}
}
if start < j {
quickSort(arr, start, j)
}
if end > i {
quickSort(arr, i, end)
}
}
}
// 快速排序(直接)实现
func quickSort2(s []int) []int {
if len(s) < 2 {
return s
}
v := s[0]
var left, right []int
for _, e := range s[1:] {
if e <= v {
left = append(left, e)
} else {
right = append(right, e)
}
}
// 实现了“quickSort(left) + v + quickSort(right)”的操作
return append(append(quickSort(left), v), quickSort(right)...)
}
func main() {
arr := []int{3, 7, 9, 8, 38, 93, 12, 222, 45, 93, 23, 84, 65, 2}
quickSort(arr, 0, len(arr)-1)
fmt.Println(arr)
}
3 简单插入排序
基本思想:构建有序序列,将未排序的数据逐次插入有序序列中,使得新序列也有序。
3.1 算法描述
- 第一个元素默认有序
- 选下一个元素,将上述序列从后向前扫描,加入有序序列中
- 若元素值小于有序序列的元素,则向前继续扫描
- 不断重复上一步,直到找到有序序列中的位置【<=新元素】
- 将新元素插入该位置后面
- 重复2~5
3.2 时间复杂度
O(n^2)
3.3 代码实现
using namespace std;
void InsertSort(int a[], int size)
{
for (int i = 1; i < size; i++)
{
int index = i;
int key = a[index]; //待排序第一个元素
int end = index-1;//代表已经排过序的元素最后一个索引数
while (end >= 0 && key < a[end])//从后向前比对
{
a[end+1] = a[end];//将有序序列最后一个值右移一位,为新元素
end--;
}
a[end+1] = key;//在第一个小于key值的后面插入新元素
}
}
int main() {
int d[] = { 12, 15, 9, 20, 6, 31, 24 };
cout << "输入数组 { 12, 15, 9, 20, 6, 31, 24 } " << endl;
InsertSort(d,7);
cout << "排序后结果:";
for (int i = 0; i < 7; i++)
{
cout << d[i]<<" ";
}
return 0;
}
package main
import (
"fmt"
)
func InsertionSort(array [6]int, n int) {
var i, j int
var tmp int
for i = 1; i < n; i++ {
tmp = array[i]
for j = i; j > 0 && array[j-1] > tmp; j-- {
array[j] = array[j-1]
}
array[j] = tmp
}
fmt.Println(array)
}
func main() {
a := [...]int{34, 8, 64, 51, 32, 21}
fmt.Println(a)
num := len(a)
InsertionSort(a, num)
}
4 希尔排序
也是插入算法的一种,将整个集合根据不同的步长分成若干个子集和【相距某个“增量”的记录组成一个子序列】,子序列内进行直接插入排序-----基本有序。
4.1 算法描述
- 选择一个间隔【通常为长度的一半】,将整个集合进行分组;
- 在组内进行排序;
- 重新设置间隔【上次分组间隔的一半】,进行分组,并组内排序
- 直到分组间隔=1,最后进行排序
其中:
排序算法为插入排序算法
间隔既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》
4.2 时间复杂度
较好的增量序列可使时间复杂度降为:O(n^(3/2)),
甚至O(n^1.3) ,大量数据时,效率比插入排序高。
4.3 代码实现
#include <stdio.h>
int shellSort(int s[], int n) /* 自定义函数 shellSort()*/
{
int i,j,d;
for (d = n/2; d >= 1; d /= 2) /*设置初始的增量值,每次循环,将增量值减半*/
{
for(i=d+1;i<=n;i++) /*从d+1:第一个gap后第一个元素开始遍历数组进行直接插入排序*/
{
s[0] = s[i];//将即将插入的元素s[i]进行缓存,监视哨
j = i-d;
while(j >= 1 && s[j] > s[i]) {//不断遍历,直到找到小于等于新元素的位置
s[j+d] = s[j];//满足条件的点要平移为新元素插入做准备
j -= d;
}
s[j+d] = s[0]//找到对应位置j,位置j的元素小于新元素,则在下一个gap点插入
}
}
return 0;
}
int main()
{
int a[11],i; /*定义数组及变量为基本整型*/
printf("请输入 10 个数据:\n");
for(i=1;i<=10;i++)
scanf("%d",&a[i]); /*从键盘中输入10个数据*/
shellSort(a, 10); /* 调用 shellSort()函数*/
printf("排序后的顺序是:\n");
for(i=1;i<=10;i++)
printf("%5d",a[i]); /*输出排序后的数组*/
printf("\n");
return 0;
}
func ShellSort(n []int,len int){
step := len/2
for ; step > 0;step=step/2 {
for i := step; i < len;i++ {
j := i-step
temp := n[i]
for j>=0 && temp < n[j] {
n[j+step] = n[j]
j=j-step
}
n[j+step] = temp
}
}
}
5 简单选择排序
选择排序的一种,从n-i+1中选出最小【大】的元素与第i个元素【进行n-i次比较】交换【1<=i<=n】。简单说就是:在未排序序列中找到最小(大)元素,存放至有序序列后,依次类推,不断取值,知道原序列为空,则新序列就是所有有序的元素。
5.1 算法描述
- 初始状态:无序区为R[1…n],有序区为空;
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R[i…n]。该趟排序从当前无序区中-选出最小【大】的元素;
- 将选出的元素R[x]与第i个元素交换,使得R[1…i]和R[i+1…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
- 进行 n-1趟结束,数组有序。
5.2 时间复杂度
虽然时间复杂度同为O(n^2),但是比较次数较多移动次数少,性能比冒泡要优一些。
稳定性较好,适合规模小的场景,且不需要额外空间。
5.3 代码实现
func SelectionSort(a []int, n int) {
if n <= 1 {
return
}
for i := 0; i < n; i++ {
// 查找最小值
minIndex := i
for j := i + 1; j < n; j++ {
if a[j] < a[minIndex] {
minIndex = j
}
}
// 交换
a[i], a[minIndex] = a[minIndex],a[i]
}
}
#include <stdio.h>
#include <stdlib.h>
#define MAX 9
//单个记录的结构体
typedef struct {
int key;
}SqNote;
//记录表的结构体
typedef struct {
SqNote r[MAX];
int length;
}SqList;
//交换两个记录的位置
void swap(SqNote *a,SqNote *b){
int key=a->key;
a->key=b->key;
b->key=key;
}
//查找表中关键字的最小值
int SelectMinKey(SqList *L,int i){
int min=i;
//从下标为 i+1 开始,一直遍历至最后一个关键字,找到最小值所在的位置
while (i+1<L->length) {
if (L->r[min].key>L->r[i+1].key) {
min=i+1;
}
i++;
}
return min;
}
//简单选择排序算法实现函数
void SelectSort(SqList * L){
for (int i=0; i<L->length; i++) {
//查找第 i 的位置所要放置的最小值的位置
int j=SelectMinKey(L,i);
//如果 j 和 i 不相等,说明最小值不在下标为 i 的位置,需要交换
if (i!=j) {
swap(&(L->r[i]),&(L->r[j]));
}
}
}
int main() {
SqList * L=(SqList*)malloc(sizeof(SqList));
L->length=8;
L->r[0].key=49;
L->r[1].key=38;
L->r[2].key=65;
L->r[3].key=97;
L->r[4].key=76;
L->r[5].key=13;
L->r[6].key=27;
L->r[7].key=49;
SelectSort(L);
for (int i=0; i<L->length; i++) {
printf("%d ",L->r[i].key);
}
return 0;
}
6 堆排序
简单选择排序的改进,利用数据结构中“堆”的概念:具有某种性质的完全二叉树----每个节点的值>=左右孩子【大顶堆】,<=左右孩子【小顶堆】。
6.1 算法描述
1.将要排序的队列中构造成一个n个元素的大顶堆,那么根节点就是最大值;
2.排序,根节点与最后一个叶子节点交换,此时末尾元素就是最大值,并将剩余n-1的节点进行计算出一个次大顶堆;
3.以此类推。这样保证最后的元素都是排序好的,最终保证有序。
6.2 时间复杂度
O(nlogn);构建堆排序次数较多,不适合个数较多的排序。
6.3 代码实现
//实现1
func buildMaxHeap(nums []int) { // 建立大顶堆
len := len(nums)
for i := len/2; i >= 0; i-- {//从最后一个父节点开始遍历,保证由底向上遍历后父节点都是最大的
heapify(nums, i)
}
}
func heapify(nums []int, parent int) { // 堆调整
len := len(nums)
left := 2 * parent + 1
right := 2 * parent + 2 //完全二叉树原理i节点的左右子节点索引为2i,2i+1【1开始计数】/2i+1,2i+2 【从0计数】
largest = parent
if left < len && nums[left] > nums[largest] {
largest = left;
}
if(right < len && nums[right] > nums[largest]) {
largest = right;
}
if(largest != parent) {//最大节点不是parent,则parent与child进行交换,反之认为
nums[parent], nums[largest] = nums[largest], nums[parent]
heapify(nums, largest);//递归遍历已经交换父节点后的子树重新调整保证大顶堆
}
}
func heapSort(arr []int) []int{
buildMaxHeap(arr);
len := len(arr)
for i := len - 1; i > 0; i-- {
arr[i], arr[0] = arr[0], arr[i]
len--
heapify(arr[0:len], 0);
}
return arr;
}
//实现2
func buildMaxHeap2(nums []int,parent,len int) { // 建立大顶堆
temp := nums[parent]
child := 2*parent+1//完全二叉树原理i节点的左右子节点索引为2i,2i+1【1开始计数】/2i+1,2i+2 【从0计数】
for child < len {
if child+1 < len && nums[child] < nums[child+1] {
child++//假如左节点小于右节点,则child选择右节点与parent比较
}
if child < len && nums[child] <= temp {
break//左右child节点都小于parent节点,则中断,本身parent已经是较大元素了,无需遍历所有子树【左右子节点都较小,比较也没有什么作用,且有整个buildMaxHeap的外循环】
}
nums[parent], nums[child]= nums[child], nums[parent]//parent 与child进行交换
parent = child//遍历该节点所有子树,使得子树也能满足堆的特征,且parent始终存放较大元素
child = child*2+1
}
nums[parent] = temp
}
func HeadSort(nums []int){
for i := len(nums)/2; i>=0; i-- {//完全二叉树原理,父节点索引i<=n/2向下取整
buildMaxHeap2(nums,i,len(nums))
}
for i := len(nums)-1;i >0; i--{
nums[0],nums[i] = nums[i],nums[0]//不断取根节点元素与最后节点进行交换
buildMaxHeap2(nums,0,i)//保证根节点nums[0]始终最大
}
}
#include<iostream>
using namespace std;
void heapSort(int a[], int n);
void maxHeap(int a[], int n);
void buildMaxHeap(int a[], int n);
int size;
int main()
{
int a[] = { 9, 12, -3, 0, 6, 8, 15, 7 };
size= sizeof(a) / 4;
heapSort(a, size);
for (int i = 0; i < 8; i++)
cout << a[i] << " ";
return 0;
}
void heapSort(int a[], int n)
{
int i;
buildMaxHeap(a, n);
for (i = size; i>1; i--)
{
swap(a[0], a[i-1]);
size = size - 1;
maxHeap(a, 1);
}
}
void buildMaxHeap(int a[], int n)
{
int i = n / 2;
for (i; i > 0; i--)
maxHeap(a, i);
}
void maxHeap(int a[], int n)
{
int leftChild, rightChild, largest;
leftChild = 2 * n;
rightChild = 2 * n + 1;
int q = sizeof(a);
if (leftChild<=size&&a[leftChild-1]>a[n-1])
largest = leftChild;
else
largest = n;
if (rightChild<=size&&a[rightChild-1]>a[largest-1])
largest = rightChild;
if (largest != n)
{
swap(a[n-1], a[largest-1]);
maxHeap(a, largest);
}
}
7 归并排序(Merge Sort)
归并排序,又称二路/多路归并排序,是采用分治法(Divide & Conquer)的另一个典型【上一个为:快速排序】应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将n个有序表合并成一个有序表,称为n-路归并。 下面主要介绍二路归并。
注:分治法(D&C):将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之。
基本思想如下:将待排序序列R[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列
7.1 算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
归并排序其实要做两件事:
(1)“分解”——将序列每次折半划分(递归实现)
(2)“合并”——将划分后的序列段两两合并后排序
- 在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。 这两个有序序列段分别为 R[low, mid] 和 R[mid+1, high]。
- 先将他们合并到一个局部的暂存数组R2中,带合并完成后再将R2复制回R中。 我们称 R[low, mid] 第一段,R[mid+1, high] 为第二段。
- 每次从两个段中取出一个记录进行关键字的比较,将较小者放入R2中,最后将各段中余下的部分直接复制到R2中。
- 经过这样的过程,R2已经是一个有序的序列,再将其复制回R中,一次合并排序就完成了。
7.2 时间复杂度
O(nlogn),空间复杂度O(n+logn),比较占用内存但是效率高稳定。
当数据量较少时,QuickSort的运行时间比MergeSort的运行时间少;
随着数据增加,MergeSort的运行时间比QuickSort的运行时间少。
7.3 代码实现
实现:递归sort+merge
func MergeSort(n []int,start,end int){
if start >= end {
return
}
mid := (start+end)/2
MergeSort(n,start,mid)
MergeSort(n,mid+1,end)
Merge(n,start,mid,end)
}
func Merge(n []int,start,mid,end int){
var temp []int
i := start
k := mid + 1
j := 0
for ;i<=mid && k<=end;j++{
if n[i] < n[k] {
temp = append(temp,n[i])
i++
}else{
temp = append(temp,n[k])
k++
}
}
if i > mid {
temp=append(temp,n[k:end+1]...)
}else{
temp = append(temp,n[i:mid+1]...)
}
copy(n[start:end+1],temp)
}
/*
1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4. 重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾
*/
#include <stdio.h>
#include <stdlib.h>
void MergeSort(int *num, int start, int end);
void Merge(int *num, int start, int mid, int end);
int main()
{
/* 归并排序(升序) */
int num[10] = {5, 1, 8, 4, 7, 2, 3, 9, 0, 6};
int length = sizeof(num) / sizeof(num[0]);
int i;
MergeSort(num, 0, length - 1);
for (i = 0; i < length; i++)
{
printf("%d ", num[i]);
}
return 0;
}
/* 将序列对半拆分直到序列长度为1*/
void MergeSort(int *num, int start, int end)
{
int mid = start + (end - start) / 2;
if (start >= end)
{
return;
}
//将序列分成两个,然后分别排序再合并
MergeSort(num, start, mid);
MergeSort(num, mid + 1, end);
Merge(num, start, mid, end);
}
void Merge(int *num, int start, int mid, int end)
{
//申请空间来存放两个有序区归并后的临时区域
int *temp = (int *)malloc((end-start+1) * sizeof(int));
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end)//两个部分的对应元素两两比较
{
if (num[i] <= num[j])
{
temp[k++] = num[i++];//将较小元素添加至临时数组
} else {
temp[k++] = num[j++];
}
}
while (i <= mid)
{
temp[k++] = num[i++];//将num[start...mid]剩余的元素放置到temp尾部中
}
while (j <= end)
{
temp[k++] = num[j++];//将num[mid+1...end]剩余的元素放置到temp尾部中
}
//将临时区域中排序后的元素,整合到原数组中
for (i = 0; i < k; i++)
{
num[start + i] = temp[i];
}
free(temp);
}
8 计数排序
非比较类排序,要求输入的数据必须有确定的范围的整数。
将输入数值转换为key值存放额外的数组中。
8.1 算法描述
- 找出排序序列的最大值和最小值
- 统计每个元素i出现的次数并记录于新数组c[i]中,并累加
- 反向填充目标数组:将每个元素i放在新数组第C[i]项,且每放一个,C[i]–
8.2 时间复杂度
当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),但是需要额外的数组空间存放每个元素出现的次数,所以空间复杂度是O(n+k),其排序速度快于任何比较排序算法。
适合于一定范围,K值不大且序列集中的情况。
8.3 代码实现
func CounterSort(arr []int, k int) {
len := len(arr)
c := make([]int, k+1)
sortindex := 0
for i := 0; i < len; i++ {
c[arr[i]]++
}
for j := 1; j < k+1; j++ {
for c[j]>0 {
a[sortindex] = j
sortindex++
c[j]--
}
}
return
}
void counting_sort(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);
}
9 桶排序
计数排序的升级,利用函数映射关系。假如输入序列服从均匀分布,则将数据分到对应的桶中,分别排序(递归或其他排序算法)。
9.1 算法描述
- 设置有大小限制的空桶。
- 将序列分别以某种规则加入桶中
- 对非空桶进行排序
- 最后进行拼接
9.2 时间复杂度
对数组进行划分数据块,时间复杂度为O(n),对每个数据块进行排序,时间复杂度取决于使用的排序算法。在极致情况下,每个桶只有一个数据,这样的时间复杂度无疑是最优的,因为每个数据块不需要进行排序。但是空间复杂度太高。
总之,桶排序的思想就是,将数组中的全部元素划分成若干个数据块,并对每个数据块分别进行排序,以此提高时间复杂度。该算法主要适合于数量比较大并且数字相对比较集中的场合。
9.3 算法实现
include <stdio.h>
#include <stdlib.h>
typedef struct node {
int key;
struct node *next;
}KeyNode;
void bucket_sort(int keys[],int size,int bucket_size) {
int i,j;
KeyNode **bucket_table = (KeyNode **)malloc(bucket_size * sizeof(KeyNode*));
for(i = 0;i < bucket_size;i++) {
bucket_table[i] = (KeyNode*)malloc(sizeof(KeyNode));
bucket_table[i]->key = 0;
bucket_table[i]->next = NULL;
}
for(j = 0;j < size;j++) {
KeyNode *node = (KeyNode *)malloc(sizeof(KeyNode));
node->key = keys[j];
node->next = NULL;
int index = keys[j]/10;
KeyNode *p = bucket_table[index];
if(p->key == 0) {
bucket_table[index]->next = node;
(bucket_table[index]->key)++;
}else {
while(p->next != NULL && p->next->key <= node->key)
p = p->next;
node->next = p->next;
p->next = node;
(bucket_table[index]->key)++;
}
}
//print result
KeyNode * k = NULL;
for(i = 0;i < bucket_size;i++)
for(k = bucket_table[i]->next;k!=NULL;k=k->next)
printf("%d ",k->key);
printf("\n");
}
int main()
{
int raw[] = {49,38,65,97,76,13,27,49};
int size = sizeof(raw)/sizeof(int);
bucket_sort(raw,size,10);
}
package main
import (
"fmt"
"container/list"
)
func bucketSort(theArray []int,num int){
var theSort [99]int
for i:=0;i< len(theArray);i++{
theSort[10]=1
if theSort[theArray[i]] !=0{
theSort[theArray[i]] = theSort[theArray[i]]+1
}else{
theSort[theArray[i]] = 1
}
}
l:=list.New()
for j:=0;j<len(theSort);j++{
if theSort[j]==0{
//panic("error test.....")
}else{
for k:=0;k<theSort[j];k++{
l.PushBack(j)
}
}
}
for e := l.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value, " ")
}
}
func main() {
var theArray = []int{10, 1, 18, 30, 23, 12, 7, 5, 18, 17}
fmt.Print("排序前")
fmt.Println(theArray)
fmt.Print("排序后")
bucketSort(theArray,11)
}
10 基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
10.1 算法描述
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成新数组;
- 对新数组进行计数排序(利用计数排序适用于小范围数的特点);
10.2 时间复杂度
基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。
基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。
10.3 代码实现
#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM_OF_KEY 8//构成关键字的组成部分的最大个数
#define RADIX 10 //基数,例如关键字是数字,无疑由0~9组成,基数就是10;如果关键字是字符串(字母组成),基数就是 26
#define MAX_SPACE 10000
//静态链表的结点结构
typedef struct{
int data;//存储的关键字
int keys[MAX_NUM_OF_KEY];//存储关键字的数组(此时是一位一位的存储在数组中)
int next;//做指针用,用于是静态链表,所以每个结点中存储着下一个结点所在数组中的位置下标
}SLCell;
//静态链表结构
typedef struct{
SLCell r[MAX_SPACE];//静态链表的可利用空间,其中r[0]为头结点
int keynum;//当前所有的关键字中最大的关键字所包含的位数,例如最大关键字是百,说明所有keynum=3
int recnum;//静态链表的当前长度
} SLList;
typedef int ArrType[RADIX];//指针数组,用于记录各子序列的首尾位置
//排序的分配算法,i表示按照分配的位次(是个位,十位还是百位),f表示各子序列中第一个记录和最后一个记录的位置
void Distribute(SLCell *r,int i,ArrType f,ArrType e){
//初始化指针数组
for (int j=0; j<RADIX; j++) {
f[j]=0;
}
//遍历各个关键字
for (int p=r[0].next; p; p=r[p].next) {
int j=r[p].keys[i];//取出每个关键字的第 i 位,由于采用的是最低位优先法,所以,例如,第 1 位指的就是每个关键字的个位
if (!f[j]) {//如果只想该位数字的指针不存在,说明这是第一个关键字,直接记录该关键字的位置即可
f[j]=p;
}else{//如果存在,说明之前已经有同该关键字相同位的记录,所以需要将其进行连接,将最后一个相同的关键字的next指针指向该关键字所在的位置,同时最后移动尾指针的位置。
r[e[j]].next=p;
}
e[j]=p;//移动尾指针的位置
}
}
//基数排序的收集算法,即重新设置链表中各结点的尾指针
void Collect(SLCell *r,int i,ArrType f,ArrType e){
int j;
//从 0 开始遍历,查找头指针不为空的情况,为空表明该位没有该类型的关键字
for (j=0;!f[j]; j++);
r[0].next=f[j];//重新设置头结点
int t=e[j];//找到尾指针的位置
while (j<RADIX) {
for (j++; j<RADIX; j++) {
if (f[j]) {
r[t].next=f[j];//重新连接下一个位次的首个关键字
t=e[j];//t代表下一个位次的尾指针所在的位置
}
}
}
r[t].next=0;//0表示链表结束
}
void RadixSort(SLList *L){
ArrType f,e;
//根据记录中所包含的关键字的最大位数,一位一位的进行分配与收集
for (int i=0; i<L->keynum; i++) {
//秉着先分配后收集的顺序
Distribute(L->r, i, f, e);
Collect(L->r, i, f, e);
}
}
//创建静态链表
void creatList(SLList * L){
int key,i=1,j;
scanf("%d",&key);
while (key!=-1) {
L->r[i].data=key;
for (j=0; j<=L->keynum; j++) {
L->r[i].keys[j]=key%10;
key/=10;
}
L->r[i-1].next=i;
i++;
scanf("%d",&key);
}
L->recnum=i-1;
L->r[L->recnum].next=0;
}
//输出静态链表
void print(SLList*L){
for (int p=L->r[0].next; p; p=L->r[p].next) {
printf("%d ",L->r[p].data);
}
printf("\n");
}
int main(int argc, const char * argv[]) {
SLList *L=(SLList*)malloc(sizeof(SLList));
L->keynum=3;
L->recnum=0;
creatList(L);//创建静态链表
printf("排序前:");
print(L);
RadixSort(L);//对静态链表中的记录进行基数排序
printf("排序后:");
print(L);
return 0;
}
package main
import (
"algorithms"
"fmt"
)
func main() {
arr := algorithms.GetArr(5, 1000)
//arr = []int{27, 38, 12, 101, 27, 16}
fmt.Println("[UNSORTED]:\t", arr)
fmt.Println("[SORTED]:\t", radixSort(arr))
}
func radixSort(arr []int) []int {
max := getMax(arr)
// 数组中最大值决定了循环次数,101 循环三次
for bit := 1; max/bit > 0; bit *= 10 {
arr = bitSort(arr, bit)
fmt.Println("[DEBUG bit]\t", bit)
fmt.Println("[DEBUG arr]\t", arr)
}
return arr
}
//
// 对指定的位进行排序
// bit 可取 1,10,100 等值
//
func bitSort(arr []int, bit int) []int {
n := len(arr)
// 各个位的相同的数统计到 bitCounts[] 中
bitCounts := make([]int, 10)
for i := 0; i < n; i++ {
num := (arr[i] / bit) % 10
bitCounts[num]++
}
for i := 1; i < 10; i++ {
bitCounts[i] += bitCounts[i-1]
}
tmp := make([]int, 10)
for i := n - 1; i >= 0; i-- {
num := (arr[i] / bit) % 10
tmp[bitCounts[num]-1] = arr[i]
bitCounts[num]--
}
for i := 0; i < n; i++ {
arr[i] = tmp[i]
}
return arr
}
// 获取数组中最大的值
func getMax(arr []int) (max int) {
max = arr[0]
for _, v := range arr {
if max < v {
max = v
}
}
return
}
11 算法比较
其中
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
在云计算之类的环境中,待排序的数据是实时生成的,在排序算法开始运行时,数据并未完全就绪,而是随着排序算法本身的进行而逐步给出的。当然也要考虑算法的并行性和串行性。
更多:https://blog.csdn.net/zhangjikuan/article/details/49095533