1、冒泡排序
冒泡排序是简单排序的一种,如同其名一样,其原理是相邻两个数比较大小,如果最终想要的是一组从小到大的顺序数据,相邻两数据,如果a[1]>a[2],用swap函数交换俩数据,使较小的数据在前面。在比较a[2]与a[3],一轮下来,可以得到a[n]是这一组数据中最大的数据。第二轮比较中,可以不再比较a[n],因为第一轮以及确定a[n]是最大的数据,第二轮循环次数为n-1。
冒泡排序简单的来说就是两重for循环,再加一个if判断语句。
void BubbleSort(int* a, int n)
{
int temp,i,j;
for (i = n-1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
if (a[j] > a[j + 1])
{
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
2、插入排序
步骤(结果递增):
1.从第一个元素开始,该元素可以认为已经被排序;
2.取下一个元素tmp,从已排序的元素序列从后往前扫描;
3.如果该元素大于tmp,则将该元素移到下一位;
4.重复步骤3,直到找到已排序元素中小于等于tmp的元素;
5.tmp插入到该元素的后面,如果已排序所有元素都大于tmp,则将tmp插入到下标为0的位置;
6.取下一个元素tmp,重复步骤2~5;
最好情况:T=O(n);
最坏情况:T=O(n*n);
void Insertion_Sort(int A[], int N) {
int p, i, tmp;
for (p = 1; p < N; p++) {
tmp = A[p];
for (i = p; i > 0 && A[i - 1] > tmp; i--) {
A[i] = A[i - 1];
}
A[i] = tmp;
}
}
补充:时间复杂度下界
定理:任意N个不同元素组成的序列平均具有N(N-1)/4个逆序对。
定理:任何仅以交换相邻俩元素来排序的算法,其平均时间复杂度为O(N*N)。
提高算法效率:
每次消去不止1个逆序对;
每次交换相隔较远的2个元素;
3、希尔排序
希尔排序是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。
希尔排序,先将待排序列进行预排序,使待排序列接近有序,然后再对该序列进行一次插入排序。
步骤(结果递增):
1、选择增量gap=length/2;
2、之后gap = gap/2;
3、增量序列,{n/2,(n/2)/2...1};
最好情况:T=O(n);
最坏情况:T=O(n*n);
void Shell_Sort(int a[], int n) {
int D,P,tmp,i;
for (D = n / 2; D > 0; D /= 2) {
for (P = D; P < n; P++) {
tmp = a[P];
for (i = P; i >= D && a[i - D] > tmp; i -= D) {
a[i] = a[i - D];
}
a[i] = tmp;
}
}
}
4、堆排序
堆的知识补充:
什么是堆:堆是一种数据结构,一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。
大根堆:每个节点的值都大于或等于其左右孩子节点的值;eg: 9 5 8 2 3 4 7 1
小根堆:每个节点的值都小于或等于其左孩子和右孩子节点的值;eg: 1 3 5 4 2 8 9 7
得到其数据规律:
大顶堆:arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2]
小顶堆:arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2]
对于数据排序,一般来说,升序排列采用小顶堆,降序排列采用大顶堆;
堆排序的基本思想:
1、将待排序的序列构造成一个大顶堆
2、顶堆的根节点就是这个无序序列的最大值
3、将其与末尾元素进行交换,此时,末尾元素是整个序列的最大值
4、将剩余的n-1个元素重新构造成一个堆,重复1-4,便能得到一个有序序列了
每次构建大顶堆时可以看到元素逐渐减少,如此就可以得到一个有序序列。
交换元素函数:
void swap(int arr[], int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
构建大顶堆函数:
void heapify(int tree[], int n, int i) {
if (i >= n) {
return ;
}
int c1 = 2 * i + 1;
int c2 = 2 * i + 2;
int max = i;
if (c1 < n && tree[c1] > tree[max]) {
max = c1;
}
if (c2 < n && tree[c2] > tree[max]) {
max = c2;
}
if (max != i) {
swap(tree, max, i);
heapify(tree, n, max);
}
}
重建顶堆函数:
void build_heap(int tree[], int n) {
int last_node = n - 1;
int parent = (last_node - 1) / 2;
int i;
for (i = parent; i >= 0; i--) {
heapify(tree, n, i);
}
}
堆排序函数:
void Heap_Sort(int tree[], int n) {
build_heap(tree, n);
int i;
for (i = n - 1; i >= 0; i--) {
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
5、归并排序
核心:有序子列的归并,分治
如果两个子列一共有n个元素,那么归并的时间复杂度是T=O(n);
归并排序:
空间复杂度:O(N);
时间复杂度:O(NlogN)
1、每一层归并的时间复杂度为O(N)
2、归并层数最大为O(logN+1)
归并排序的基本思想:
1、当我们要排序一个数组时,归并排序法首先要将这个数组分成一半
2、然后把左边的数组排序,右边的数组排序
2.1、分别再把左边的数组分成一半,右边的数组分成一半
2.2、再把每个部分先排序
2.3、划分到每个部分就只有一个元素了,此时就不用排序,进行预测简单的归并就好
3、再把左右两边有序的子列归并
4、归并到上一个层级之后继续归并,归并到最高的层级,直至最后归并完成
基本思路就是将数组分成A,B两组,如果A,B两组组内的数据都是有序的,就可以认为组内以及达到有序,如果不是,可以将A,B俩组各自再分成俩组。以此类推,当最后分出来的小组只有一个数据时,可以认为这个小组组内已经达到有序(只有一个元素的区域,本身就是有序的,只需要被归并即可),然后合并相邻的俩个小组就可以了。整体思路就是先递归分解数列,再合并数列。
归并排序统一函数接口:
void Merge_Sort(int arr[], int n) {
int* tempA = (int*)malloc(n * sizeof(int));
if (tempA) {
msort(arr, tempA, 0, n - 1);
free(tempA);
}
else {
printf("error");
}
}
归并排序:
void msort(int arr[], int tempA[], int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
msort(arr, tempA, left, mid);
msort(arr, tempA, mid + 1, right);
merge(arr, tempA, left, mid, right);
}
}
合并:
void merge(int arr[], int *tempA, int left, int mid, int right) {
int l_pos = left;
int r_pos = mid + 1;
int pos = left;
while (l_pos <= mid && r_pos <= right) {
if (arr[l_pos] < arr[r_pos]) {
tempA[pos++] = arr[l_pos++];
}
else
tempA[pos++] = arr[r_pos++];
}
while(l_pos <= mid)
tempA[pos++] = arr[l_pos++];
while(r_pos <= right)
tempA[pos++] = arr[r_pos++];
while (left <= right) {
arr[left] = tempA[left];
left++;
}
}
补充:递归算法
递归算法:在计算机科学中,称程序调用自身的编程技巧称为递归,递归作为一种算法在程序设计过程中较为经常使用。它通常将一个大型问题层层转换成一个个与原问题相似的小问题来求解。递归程序大大的缩减了程序的代码量。
递归三要素:
1、明确递归终止条件;
2、给出递归终止时的处理办法;
3、提取重复的逻辑,缩小问题规模。
递归与循环的主要区别就是递归有明确的终止条件,递归完成,明确得出递归的答案,循环可以设置循环条件,但最终不会返回循环答案。
如归并排序中的递归算法:分而治之
void msort(int arr[], int tempA[], int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
msort(arr, tempA, left, mid);
msort(arr, tempA, mid + 1, right);
merge(arr, tempA, left, mid, right);
}
}
时间复杂度:T(N)=T(N/2)+T(N/2)+O(N) ———— T(N)=O(NlogN)
由此可见归并排序是较稳定的一种排序算法。
附完整代码
#include <iostream>
#include <stdio.h>
#include<stdlib.h>
//冒泡排序
void BubbleSort(int* a, int n);
//插入排序
void Insertion_Sort(int A[], int N);
//希尔排序
void Shell_Sort(int a[], int n);
//堆排序
void Heap_Sort(int tree[], int n);
//归并排序
void Merge_Sort(int arr[], int n);
void main()
{
int arr[] = { 2,1,6,3,8,4,9,5,7,0 };
//BubbleSort(arr, 10); //冒泡排序
//Insertion_Sort(arr, 10); //插入排序
//Shell_Sort(arr, 10); //希尔排序
//Heap_Sort(arr, 10); //堆排序
Merge_Sort(arr, 10); //归并排序
int k;
for (k = 0; k < 10; k++)
{
printf("%d", arr[k]);
}
}
//冒泡排序
void BubbleSort(int* a, int n)
{
int temp,i,j;
for (i = n-1; i>0; i--)
{
for (j = 0; j < i; j++)
{
if (a[j] > a[j + 1])
{
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
//插入排序
void Insertion_Sort(int A[], int N) {
int p, i, temp;
for (p = 1; p < N; p++) {
temp = A[p];
for (i = p; i > 0 && A[i - 1] > temp; i--) {
A[i] = A[i - 1];
}
A[i] = temp;
}
}
//希尔排序
void Shell_Sort(int a[], int n) {
int D,P,tmp,i;
for (D = n / 2; D > 0; D /= 2) {
for (P = D; P < n; P++) {
tmp = a[P];
for (i = P; i >= D && a[i - D] > tmp; i -= D) {
a[i] = a[i - D];
}
a[i] = tmp;
}
}
}
//堆排序
void swap(int arr[], int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
void heapify(int tree[], int n, int i) {
if (i >= n) {
return ;
}
int c1 = 2 * i + 1;
int c2 = 2 * i + 2;
int max = i;
if (c1 < n && tree[c1] > tree[max]) {
max = c1;
}
if (c2 < n && tree[c2] > tree[max]) {
max = c2;
}
if (max != i) {
swap(tree, max, i);
heapify(tree, n, max);
}
}
void build_heap(int tree[], int n) {
int last_node = n - 1;
int parent = (last_node - 1) / 2;
int i;
for (i = parent; i >= 0; i--) {
heapify(tree, n, i);
}
}
void Heap_Sort(int tree[], int n) {
build_heap(tree, n);
int i;
for (i = n - 1; i >= 0; i--) {
swap(tree, i, 0);
heapify(tree, i, 0);
}
}
//合并
void merge(int arr[], int *tempA, int left, int mid, int right) {
//标记左半区第一个未排序的元素
int l_pos = left;
//标记右半区第一个未排序的元素
int r_pos = mid + 1;
//临时数组元素下标
int pos = left;
//合并
while (l_pos <= mid && r_pos <= right) {
if (arr[l_pos] < arr[r_pos]) {
tempA[pos++] = arr[l_pos++];
}
else
tempA[pos++] = arr[r_pos++];
}
//合并左半区剩余的元素
while(l_pos <= mid)
tempA[pos++] = arr[l_pos++];
//合并右半区剩余的元素
while(r_pos <= right)
tempA[pos++] = arr[r_pos++];
//把临时数组中合并后的元素复制回原来的数组
while (left <= right) {
arr[left] = tempA[left];
left++;
}
}
//归并排序
void msort(int arr[], int tempA[], int left, int right) {
//如果只有一个元素,那么就不需要继续划分
// 只有一个元素的区域,本身就是有序的,只需要被归并即可
if (left < right) {
//找中间点
int mid = (left + right) / 2;
//递归划分左半区
msort(arr, tempA, left, mid);
//递归划分右半区
msort(arr, tempA, mid + 1, right);
//合并已经排序的部分
merge(arr, tempA, left, mid, right);
}
}
//归并排序入口
void Merge_Sort(int arr[], int n) {
int* tempA = (int*)malloc(n * sizeof(int)); //分配一个辅助数组
if (tempA) {
msort(arr, tempA, 0, n - 1);
free(tempA);
}
else {
printf("error");
}
}