1、概述
大,一次性无法全部读入到内存中,我们要介绍的排序算法主要是内排序。在内排序算法中,如果在排序的最终结果中,各元素的次序依赖于它们之间的比较,那么我们称此类排序算法为比较排序。各排序的层次结构图如下所示:排序算法包括内排序和外排序,所谓的内排序就是要排序的数据全部在内存中,而外排序是因为要排序的数据太
2、比较排序
1、交换排序类
1、冒泡排序
基本思想:用第一个元素依次和后面的元素相比较,如果前者比后者大那么就交换二者,交换完成后
最后一个元素就是最大的元素,循环下去直到完成排序。冒泡排序在最好的情况下(元素是按照从小到大的顺序存储的)运行的时间为O(n),最坏和平均时间复杂度为 O(n^2)。代码如下:
void buble_sort(int array[],int n) { int i, j,temp; bool didswap; for (i = n; i > 0;i--) { didswap = false; for (j = 0; j < i;j++) { if (array[j] < array[j+1]) { temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; didswap = true; } } if (didswap == false) return; } }
2、快速排序
元素都大于 a[q],然后对a[p,..,q-1]、a[q+1,..,r]进行递归排序,求取a[q]元素的过程如下所示。快速排序的最好和平均运行时间复杂度为O(n*logn),最坏情况下(每次分解数组的时候都是一个空数组和一个n-1长度的数组)的时间复杂度为O(n^2)基本思想:从数组a[p,....,r]中选取一个元素a[q],使得a[p,..,q-1]中的元素都不大于a[q],a[q+1,..,r]中的
int partion(int array[], int start, int end) { int key = array[end]; int i, j; int temp; for (i = start,j = start; i < end;i++) { if (array[i] < key) { temp = array[i]; array[i] = array[j]; array[j] = temp; j++; } } temp = array[end]; array[end] = array[j]; array[j] = temp; return j; }
void quick_sort(int array[], int start, int end) { if (start < end) { int q = partion(array, start, end); quick_sort(array, start, q - 1); quick_sort(array, q + 1, end); } }
2、插入排序类
1、直接插入排序
序,排序步骤如下。直接插入排序的最坏时间复杂度为O(n^2),最好情况为 O(n),平均情况为O(n^2)。基本思想:将待排序的元素插入到已经排好序的子数组中,当所有元素插入完毕后,则该素组就排好
void insert_sort(int array[], int n) { int key; int i,j; for (i = 1; i < n;i++) { key = array[i]; j = i - 1; while (j >= 0 && array[j] > key) { array[j + 1] = array[j]; j--; } array[j + 1] = key; } }
2、希尔排序
希尔排序(Shell Sort),也称递减增量排序算法,是插入排序的一种更高效的改进版本。该算法的基本思想是:把数组下标按照增量进行分组,对每组元素进行直接插入排序;随着增量的减小每组的元素个数不断增加,直到增量减为1为止,算法结束。希尔排序的最坏时间复杂度为O(n^2),最好情况为O(n^1.3),平均情况为O(n*logn)~O(n^2)。算法过程如下图所示:
void shell_insert_sort(int array[], int n) { int gap, i, j, k,key; /*对数组进行分组*/ for (gap = n / 2; gap > 0;gap/=2) { /*对每组元素进行直接插入排序*/ for (i = gap; i < n;i+=gap) { j = i - gap; key = array[i]; while (j>=0 && array[j] > key) { array[j + gap] = array[j]; j -= gap; } array[j + gap] = key; } } }
3、选择排序类
1、选择排序
基本思想:每次从待排序的序列中选出最小(或最大)的元素放在序列的起始位置,直到所有待排序的序列排完。选择排序的最好和最坏情况下的时间复杂度均为O(n^2)。
void select_sort(int array[], int n) { int i, j, low,temp; for (i = 0; i < n;i++) { low = i; /*找出序列中的最小元素的下标*/ for (j = i; j < n;j++) { if (array[low] > array[j]) low = j; } /*用序列中的最小元素与起始元素交换*/ temp = array[low]; array[low] = array[i]; array[i] = temp; } }
2、堆排序
堆是一个数组,他可以被看成是一个近似的完全二叉树,树的根节点是A[0],这样给定一个节点i我们很容易计算出其左孩子和右孩子的下标:left = 2*i + 1,right = 2*i + 2.。二叉堆又分为最大堆和最小堆,节点的值都要满足堆的性质,在最大堆中节点满足A[i] >= A[left]、A[i] >= A[right],在最小堆中节点满足A[i] <= A[left]、A[i] <= A[right]。
最大堆排序的基本思想:给定待排序的数组a首先构造一个最大堆,因此最大堆的根节点即a[0]是堆中最大
的元素,将堆顶元素a[0]和a[n]进行交换,交换之后我们还要维护最大堆的性质,此时堆中的有效元素个数为n-1,然后再将a[0]和a[n-1]进行交换,依次进行下去直到堆中只有一个有效元素为止。堆排序的最好情况和最坏情况下的时间复杂度都为O(n*logn)。如何构造一个最大堆?我们采用自底向上的方式创建一个最大堆,下图给出了解释
void keep_max_heap(int array[], int n,int i) { /*计算数组下标为i的左孩子的下标和右孩子的下标*/ int left = 2*i + 1; int right = 2*i + 2; int temp; int largst; /*找出三者之间最大数的下表*/ if (left < n && array[i] < array[left]) largst = left; else largst = i; if (right < n && array[right] > array[largst]) largst = right; if (largst != i) { temp = array[largst]; array[largst] = array[i]; array[i] = temp; keep_max_heap(array, n, largst); } } void heap_sort(int array[], int n) { /*先创建一个最大堆*/ int start = n / 2 - 1; for (int i = start; i >= 0; i--) keep_max_heap(array, n, i); /*从最大堆中取出array[0]与array[n-1]进行交换*/ int temp; for (int j = n - 1; j >= 0;j--) { temp = array[0]; array[0] = array[j]; array[j] = temp; keep_max_heap(array, j, 0); } }
4、归并排序
分解成两个子数组,对每个子数组进行排序,然后将排序好的子数组进行归并操作。归并排序的最好和最坏情况下的时间复杂度都为O(n*logn)。所谓的归并操作是对于已排序好的数组a和b,选取数组a、b中的第一个元素进行比较,如果a中的元素大于b中的元素,那么就把b中的此元素赋值给数组c,并将b中的元素删除,否则 就把a中的元素赋值给数组c,并将a中的此元素删除,循环下去直到其中一个数组为空,然后将另一个数组的所有元素依次赋值给数组c。归并操作的图解如下:归并排序是建立在归并操作上的一种有效算法,是分治策略的一种典型应用。基本思想:将待排序的数组
void merge(int a[], int start, int mid, int end) { int i = start, j = mid + 1; int k = 0; int n = mid - start + 1; int m = end - mid; int *c = (int *)malloc(sizeof(int)* (m + n)); while (i <= mid && j <= end) { if (a[i] < a[j]) { c[k] = a[i]; i++; } else { c[k] = a[j]; j++; } k++; } if (i > mid) { while (j <= end) { c[k] = a[j]; j++; k++; } } else { while (i <= mid) { c[k] = a[i]; i++; k++; } } /*将排好序的数组C中的元素复制到数组a中*/ for (int r = 0; r < (m + n);r++) a[r + start] = c[r]; free(c); } void merge_sort(int array[], int start,int end) { if (end <= start) return; int mid = (start + end) / 2; merge_sort(array, start, mid); merge_sort(array, mid + 1, end); merge(array, start, mid, end); }
3、线性时间排序
1、计数排序
为对 输入序列做了假设,因此计数排序可以在O(n)时间内完成。基本思想:统计序列a中元素值为i出现的次数,记录在数组c[i]中,计算出所有比i元素大的元素个数m,那么i元素则放在数组b的第m个位置。因为正统的基数排序算法需要一个额外的数组b,这样浪费了内存,因此我们队算法进行改进:计数排序对待排序的序列有要求:序列中的元素个数为n,序列中的元素取值范围为0~k,必须满足k<n,因
void count_sort(int array[], int n) { int *c = (int *)malloc(sizeof(int)* n); int i; for (i = 0; i < n; i++) c[i] = 0; /*统计array[i]的元素个数*/ for (i = 0; i < n;i++) c[array[i]] = c[array[i]] + 1; int z = 0; for (i = 0; i < n;i++) { while (c[i]-- > 0) array[z++] = i; } }
2、基数排序
以达到 排序的目的。对于我们使用的阿拉伯数字,其基数是0~9,因此我们可以用十个“桶”来存放这些元素。在基数排序的过程中,我们先排个位数,根据个位数的数值分配存放的桶,然后再把桶中的元素收集起来,然后再依次排序十位、百位......,最后完成排序。基数排序的时间复杂度为O(n)。排序过程如下所示:基数排序属于“分配式”排序,又称“桶子法”,顾名思义它根据键值的部分资讯将元素分配的某个“桶”中,籍
//辅助链表数据结构 typedef struct node { int data; struct node *next; }LNode,*Link; void radix_sort(int array[], int n, int m) { int i,j,postion; /*从低位到高位排序*/ for (i = 1; i <= m; i++) { Link bucket[10]; /*对链表初始化*/ for (j = 0; j < 10; j++) init_link(&bucket[j]); /*对数组中的元素,按照第i位放入桶中,即分配桶*/ for (j = 0; j < n;j++) { postion = get_position(array[j], i); insert_link(bucket[postion], array[j]); } /*从桶中收集元素*/ int k = 0; for (j = 0; j < 10;j++) { if (bucket[j] != NULL) { Link temp = bucket[j]; while (!is_empty_link(temp)) { delete_link(temp, &array[k]); k++; } } } } } int get_position(int num, int m) { for (; m > 1; m--) num /= 10; return(num % 10); } void init_link(Link *link) { *link = (Link)malloc(sizeof(LNode)); (*link)->next = NULL; } bool is_empty_link(Link link) { if (link->next != NULL) return false; return true; } void insert_link(Link link, int e) { Link temp = (Link)malloc(sizeof(LNode)); temp->data = e; temp->next = NULL; Link x = link->next; Link y = x; while (x != NULL) { y = x; x = x->next; } if (y == NULL) link->next = temp; else y->next = temp; } bool delete_link(Link link, int *e) { if (is_empty_link(link)) return false; Link temp = link->next; link->next = temp->next; *e = temp->data; free(temp); return true; }
3、桶排序
桶排序假设输入的数据服从均匀分布,并独立地分布在[0,1)区间,由于对输入数据做了假设,因此桶排序
的速度也很快,平均情况下它的时间代价为O(n)。基本思想:将 [0,1)区间划分为n个相同大小的子区间,然后将n个输入分别放到各个桶中,并对每个桶进行排序,遍历每个桶依次把数据列出来即可。