排序简介
简单排序:插入排序,选择排序,冒泡排序。(只要掌握了冒泡排序即可)他们的时间复杂度都为O(N^2)。
插入排序:从第一个数据开始,把第一个数据放入有序队列(有序队列一开始为空)进行排序,依次进行,直到排序完整。(把未排序的数据放入有序队列中)
希尔排序: 插入排序的改版。也就是多个数据进行插入排序。
选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
冒泡排序:顾名思义,把一个最小(大)的数据冒到最上面去,在冒的过程中,每次只比较两个数据,谁小(大)谁进入泡泡继续往上冒。
代码:
#include <stdio.h>
void bubble_sort(int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = (int) sizeof(arr) / sizeof(*arr);
bubble_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
return 0;
}
重点:
快速排序: 跟冒泡相似,但是比冒泡快很多,因为快排是左右开弓,首先找一个基数k,以k为基准,把整个数据分为两个部分,左边:全部小于k。右边:全部大于k。然后将左右边的数据以同样的方法周而复始(即递归)。
代码如下:
`#include<iostream>
using namespace std;
//函数声明
void quick_sort(int* a, int begin, int end);
int main()
{
int a[10];
for (int i = 0; i < 10; i++)
{
cin >> a[i];
}
quick_sort(a, 0, 9);
for (int i = 0; i < 10; i++)
{
cout << a[i];
}
return 0;
}
void quick_sort(int* a, int begin, int end)
{
int k;//基数
int n, m;//哨兵
n = begin;
m = end;
k = a[begin];
if (n > m)//递归的终止条件
{
return;
}
while (n != m)
{
while (m > n && a[m] > k)
m--;
while (n < m && a[n] < k)
n++;
if (n != m)
{
int l;
l = a[n];
a[n] = a[m];
a[m] = l;
}
}
a[n] = k;//把基数放到正确的位置
k = n;//以基数分界点,分别递归解决左右两边的排序
quick_sort(a, 0, k - 1);
quick_sort(a, k+1, end);
}`
快排2.0: 简单的快排的时间复杂度为O(N^2),这里的加强版的时间复杂度可以降为O(N * logN)。我们利用分治的思想:
第一步:从数列中随机选取一个数与数列的最后一个数交换(降时间复杂度的关键),将这个选取的数作为基准,把数列分成三部分,小于基准的放左边,等于的放中间,大于的放右边,这个过程我们称为partition。之后我们递归,用左边和右边的重复进行partition。
第二部:如何定义partition的部分,首先我们定义一个小于区域的右边界,再定义一个大于区域的左边界,i = 0,1>>当第i个数小于基准时,用第i个数跟小于区域右边界+1个数进行交换,再将小于区域的右边界扩大一位,i++;2>>当第i个数等于基准时,i++;3>>当第i个数大于基准时,用第i个数跟大于区域左边界-1个数进行交换,再将大于区域的左边界减小一位,i不变;然后将基准的数与大于区域的左边界的数进行交换,最后将返回等于区域的左边界和右边界;
代码:
#include<iostream>
#include<ctime>
using namespace std;
#define random(x) (rand()%x)
void sp(int* a, int l, int r)//交换函数
{
int tmp = 0;
tmp = a[l];
a[l] = a[r];
a[r] = tmp;
}
int* partition(int* a, int l, int r)
{
int p[2] = { 0 };//存储等于区域的左右边界
int less = l - 1;//左边界
int more = r;//右边界
while (l < more)
{
if (a[l] < a[r])//第一种情况
sp(a, ++less, l++);
else if (a[l] > a[r])//第二种情况
sp(a, --more, l);
else//相等时
l++;
}
sp(a, more, r);
p[0] = less + 1;
p[1] = more;
return p;
}
void quick_sort(int* a, int l, int r)
{
if (l < r)
{
srand((int)time(0));
int s = l+ random(r-l+1);//降低复杂度的关键点
sp(a, s, r);
int* p;
p = partition(a, l, r);
quick_sort(a, l, p[0] - 1);
quick_sort(a, p[1] + 1, r);
}
}
int main()
{
int a[6] = { 3,2,5,4,7,6 };
quick_sort(a, 0, 5);//这里的a是递归的非决定性参数
for (int i = 0; i <= 5; i++)
{
cout << a[i] << endl;
}
}
归并排序: 利用了分治的思想和递归。我们的整体思想是先把一个数列分成两半,保证数列的两半都是有序的(merge)。在写递归时,把递归的流程图画出来,从大局观看更容易理解。
#include<iostream>
using namespace std;
void merge_wp(int* a, int l, int m, int r)
{
int* h = new int[r - l + 1];//存储目前归并好的临时数组
int p1 = l;//从左边一半的第一个开始的标记
int p2 = m + 1;//从右边一半的第一个开始的标记
int i = 0;//临时数组的下标
while (p1 <= m && p2 <= r)//当标记都不超过各自那半数组的界限时
h[i++] = a[p1] <= a[p2] ? a[p1++] : a[p2++];//标记各自往后移动进行排序
while (p1 <= m)
h[i++] = a[p1++];//当左边的数组还剩下元素,则依次添加到后面
while (p2 <= r)
h[i++] = a[p2++];//同理
for (int j = 0; j <r-l+1; j++)
a[l+j] = h[j];
delete[] h;
}
void merge_sort(int* a, int l, int r)
{
if (l == r)//如果只有一个元素则返回
return;
int mid = l + ((r - l) >> 1);//这样取中点不会出现越界
merge_sort(a, l, mid);//利用merge_wp保证左边有序
merge_sort( a, mid + 1, r);//利用merge_wp保证右边有序
merge_wp( a, l, mid, r);//归并操作
}
int main()
{
int a[6] = { 3,2,5,4,7,6 };
merge_sort(a, 0, 5);//这里的a是递归的非决定性参数
for (int i = 0; i <= 5; i++)
{
cout << a[i] << endl;
}
}
堆排序: 首先要了解堆结构,堆结构其实就是STL中的优先级队列(priority_queue),在一个数列当中,将最大的数放在完全二叉树的顶点,从大到小构成一颗完全二叉树,形成大根堆,反则是小根堆,在这种大根堆结构当中我们可以知道最大的数就是第一个位置的数,堆排序就是在大根堆的基础上多一步操作(heapify),把第一个位置的数与最后一个位置的数进行交换,再把最后一个位置之前的树进行大根堆排序,周而复始,数列就能升序排序。
#include<iostream>
using namespace std;
void swap(int* a, int i,int j)
{
int tmp;
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
void heapinsert(int* a, int index)//从index位置插入数据,形成大根堆(往上移动)
{
while (a[index] > a[(index - 1) / 2])//只要该位置的数据大于父节点就交换
{
swap(a, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
void heapify(int* a, int index, int heapsize)//从index位置的数开始往下移动
{
int left = index * 2 + 1;//左孩子的下标
while (left < heapsize)//有孩子才往下移动
{
int largest = left + 1 < heapsize && a[left + 1] > a[left] ? left + 1 : left;//存在右孩子且右孩子大于左孩子 选出最大的数的位置
largest = a[largest] > a[index] ? largest : index;//看孩子是否大于父节点index
if (largest == index)//当移动到合适的位置停止
return;
swap(a, largest, index);
index = largest;
left = index * 2 + 1;
}
}
int main()
{
int a[6] = { 4,3,7,8,1,9 };
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
heapinsert(a, i);//形成大根堆
}
int heapsize = sizeof(a) / sizeof(a[0]);//限制heapify的个数,heapity的作用就是找出一个最大的数 放在最后面,下一次就对其他的heapify
swap(a, 0, --heapsize);
while (heapsize > 0)
{
heapify(a, 0, heapsize);
swap(a, 0, --heapsize);
}
for (int i = 0; i < 6; i++)
{
cout << a[i] << endl;
}
}