目录
1.简单选择排序
简单选择排序的方法十分简单,也十分的直观。
工作的原理便是,每次遍历未排序数组,找到最小的(或者最大的)数字,将他和未排序部分的第一个数字交换,这样就可以实现,排序功能。
实际演示如下:
相关代码如下:
//简单选择排序
int* ex5_3_sort1(int* array, int length) {
if (array == NULL) {
cout << "error,ex5_3_sort1" << endl;
return NULL;
}
int point;
int temp;
for (int i = 0; i < length; i++) {
point = i;
//找出最小
for (int j = i + 1; j < length; j++) {
if (array[point] > array[j]) {
point = j;
}
}
//交换
temp = array[point];
array[point] = array[i];
array[i] = temp;
}
return array;
}
时间复杂度为O(n*n),空间复杂度为O(1) 。
为不稳定排序
2.插入排序
取出未排序的第一个数字和已排序的序列比较,插入到合适的位置。
1、将原表分为已排序表和未排序表:
已排序表:[R1 R2 …… Ri-1]
未排序表:[Ri Ri+1 …… Rn]
2、每次从未排序表中取出表头元素按排序关系插入到排序表中;当未排序表为空时,排序结束。
3、需进行(n-1)趟插入排序
4、插入运算为从后向前比较:先取Ri与Ri-1比较,若Ri<Ri-1,则Ri-1后移一个位置,再取下一个元素Ri-2与Ri比较,依此类推,直到找到 插入位置,就在该位置插入元素。
演示如下:
相关代码如下:
//插入排序
int* ex5_3_sort2(int* array, int length) {
if (array == NULL) {
cout << "error,ex5_3_sort2" << endl;
return NULL;
}
int temp;
int j;
for (int i = 0; i < length; i++) {
temp = array[i];//待排序中的第一个元素
for (j = i - 1; j >= 0; j--) {
if (temp > array[j]) {//如果大于则赋值,并完成该元素的排序
array[j+1] = temp;
break;
}
array[j + 1] = array[j];//如果小于则移动元素继续
}
if (j == -1) {
array[0] = temp;
}
}
return array;
}
时间复杂度最好情况为O(n),最坏为O(n*n) ,空间复杂度为O(1)
如果我们,将待排序的记录R[i] 插入到已排好序的记录子表R[1…i-1]中时,由于R1, R2 ,…, Ri-1已排好序,则查找插入位置可以用“折半查找”实现,则直接插入排序就变成为折半插入排序。
为稳定排序
3.冒泡排序
基本方法:从表首元素开始,两两比较,若 Ri>Ri+1 ,则两个元素交换。经过一趟冒泡排序后,具有最大排序码的数据元素就移到了最后,而具有小排序码的数据元素则像气泡一样逐渐上浮; 再对前n-1个数据元素冒泡排序,依此类推,直到在一趟冒泡排序过程中没有进行过交换元素的操作,整个排序过程结束。
演示如下:
注:图片来自https://visualgo.net
相关代码如下:
//冒泡排序
int* ex5_3_sort3(int* array, int length) {
if (array == NULL) {
cout << "error,ex5_3_sort3" << endl;
return NULL;
}
int temp;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (array[j] > array[j + 1]) {//如果前一个大于后一个就交换
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array;
}
时间复杂度最坏情况为O(n*n),最好情况为O(n),空间复杂度为O(1)
为稳定排序
其实我们还可以对该方法进行改进,比如我们发现如果这个排序序列一开始就是排好序的,那么我们进行那么多次比较都是无用的。
因此我们可以加一个判断条件,判断如果在一次大的循环后,数组没有发生改变则,可以结束了,可以减少无效的比较。
则代码可以变成
//冒泡排序
int* ex5_3_sort3(int* array, int length) {
if (array == NULL) {
cout << "error,ex5_3_sort3" << endl;
return NULL;
}
int flag = 1;
int temp;
for (int i = 0; i < length && flag == 1; i++) {
flag = 0;
for (int j = 0; j < length - i - 1; j++) {
if (array[j] > array[j + 1]) {//如果前一个大于后一个就交换
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
flag = 1;
}
}
}
return array;
}
但很可惜时间复杂度和空间复杂度并没有变化。
4.快速排序
从待排序的n个数据元素中任意选一个数据元素x[i]作为基准元素;
调整该序列中各个数据元素的位置,使得x[i]之前的数据元素的排序码都小于x[i]的排序码,x[i]之后的数据元素的排序码都大于x[i]的排序码,从而确定了x[i]在该序列中的位置;
此时,该序列被分割成2个独立的无序子序列,其中前一子序列中所有元素排序码均不大于后一子序列中元素排序码。然后再对这2个子序列分别进行快速排序,最后达到整个序列有序。
演示如下:
相关代码如下:
//快速排序
int* ex5_3_sort4(int* array, int left , int right) {
if (array == NULL) {
cout << "error,ex5_3_sort4" << endl;
return NULL;
}
if (left < right)
{
int i = left;
int j = right;
int num = array[left];
//这里选定第一个数为判断数
while (i < j)
{
while (i < j && array[j] >= num) {//从右边开始寻找小于该判断数的数字
j--;
}
if (i < j) {
array[i++] = array[j];
}
while (i < j && array[i] < num) {//从左边开始寻找大于该判断数的数字
i++;
}
if (i < j) {
array[j--] = array[i];
}
}
array[i] = num;
array = ex5_3_sort4(array, left, i - 1);//递归实现
array = ex5_3_sort4(array, i + 1, left);
}
return array;
}
这里使用的实现方法只是快排的一种,具体方式如下,另外还有很多种方法也可实现快排
时间复杂度,最好情况为O(nlogn),最坏情况为 O(n*n),平均情况为O(nlogn)
空间复杂度为O(logn)
为不稳定排序
5.归并排序
归并排序的特点是分而治之,将原来的大的问题,变为2个或者多个子问题。
而我们这里的大问题是给一个序列排序,那么他的子问题我们就可以看成,给他的子序列排序。
最后我们把排好序的子序列整合在一起便解决我们的大问题。
效果如下:
我们先把主序列,不断地拆分
当拆分到最小单元后,子子子.......序列就很好排序了,如长度为1的话就不用排序,长度为2,就比较一下之后交换。
当子子子.......序列解决后,我们就利用解决好的子问题去合并,解决更大的问题。
我们这里得到了2个有序的子序列,那么我们只需要利用2个有序序列的合并,就可以解决更大的问题了。
最终一层层返上去,就可以实现排序的效果。
演示如下:
注来源如下 https://github.com/hustcc/JS-Sorting-Algorithm
相关代码如下:
递归版
//归并排序(递归)
int* ex5_3_sort5(int* array, int left, int right) {
if (array == NULL) {
cout << "error,ex5_3_sort4" << endl;
return NULL;
}
if (right - left == 0 ) {//长度为1
return array;
}
else if (right - left == 1) {//长度为2
int a = array[left], b = array[right];
if (a > b) {//比较
array[left] = b;
array[right] = a;
}
return array;
}
else {//长度大于2
array = ex5_3_sort5(array, left, (left + right) / 2);
array = ex5_3_sort5(array, ((left + right) / 2) + 1, right);//递归调用
int temp[200];//创建空间用于实现2个有序序列的合并可根据实际情况修改
int i = left, k = 0, j = ((left + right) / 2) + 1;
for (; i <= (left + right) / 2 && j <= right; ) {
if (array[i] < array[j]) {
temp[k] = array[i];
i++;
}
else {
temp[k] = array[j];
j++;
}
k++;
}
while(i <= (left + right) / 2) {
temp[k] = array[i];
i++;
k++;
}
while (j <= right) {
temp[k] = array[j];
j++;
k++;
}
k--;
for (int n = right; k >= 0; k--, n--) {//将排好序的序列重新输入回array
array[n] = temp[k];
}
return array;
}
}
非递归版
//归并排序(非递归)
int* merge_sort(int list[],int num) {
int s = 1;
int step = 0;
int low, high, mid;
int temp[200];//创建空间用于实现2个有序序列的合并可根据实际情况修改
while (s < num) {
step = 2 * s;
for (int i = 0; i <= num - 1; i = i + step) {
low = i;
mid = i + s - 1;
high = i + 2 * s - 1;
if (high > num - 1) high = num - 1;
if (high > mid) {//合并有序子序列
int m = low, k = low, n = mid+1;
for (; m <= mid && n <= high; ) {
if (list[m] < list[n]) {
temp[k] = list[m];
m++;
}
else {
temp[k] = list[n];
n++;
}
k++;
}
while (m <= mid) {
temp[k] = list[m];
m++;
k++;
}
while (n <= high) {
temp[k] = list[n];
n++;
k++;
}
for (m = low; m <= high; m++) list[m] = temp[m];
}
}
s = 2 * s; //子序列解决完成,完成更大的序列
}
return list;
}
时间复杂度为 O(nlogn),空间复杂度为O(n)
为稳定排序
6.堆排序
这里使用堆排序需要引入完全二叉树的概念。
完全二叉树就是一个
1、从作为第一层的根开始,除了最后一层之外,第N层的元素个数都必须是2的N次方;第一层2个元素,第二层4个,第三层8个,以此类推。
2、而最后一行的元素,都要紧贴在左边,换句话说,每一行的元素都从最左边开始安放,两个元素之间不能有空闲,具备了这两个特点的树,就是一棵完全二叉树。
而我们的堆就是一个特殊的完全二叉树分为小根堆和大根堆,这里以小根堆举例
每个结点的子结点都比自身小,且为完全二叉树,这就是一个堆。
而我们这里便可以根据完全二叉树的性质,用数组来存放堆。(数组下表从1开始,0下标空出来)
所有结点都满足:
1、一个结点的父亲节点下标=该结点下标/2(采用地板除)
2、一个结点的子结点下表=该结点下标*2(左儿子)或者 该结点下标*2+1(右儿子)
如上图例子我们的堆便是[null 2 3 17 16 4 20]。(其中下标0为空出来的)
这样我们将序列做成小根堆,然后不断地将根结点出堆,就可以实现我们的堆排序了。
但是我们遇到了2个问题:
1、怎么做成小根堆
2、根结点出堆后怎么维护堆的属性
这里我们想到是编写插入和出堆函数便是可以实现。
对于插入函数:
我们选择实现的方法是将待插入的数字放到数组最后一个位置,之后不断地和父结点比较,如果该节点更小就交换,并重复该步骤
对于出堆函数:
我们选择实现的方法是将根结点出堆之后,将最后一个结点给搬移到根结点,之后和左右结点中较小并且小于该结点的交换,并重复该步骤
这样便能实现我们的堆排序
演示如下:
建堆过程
出堆过程
相关代码如下:
//插入函数
void insert(int *temp,int num ,int length) {
int a = length + 1;
temp[a] = num;
while (a / 2 >= 1) {
if (temp[a] < temp[a / 2]) {//和父结点比较
int b = temp[a];
temp[a] = temp[a / 2];
temp[a / 2] = b;
a = a / 2;
}
else {
break;
}
}
}
//出堆函数
int pop(int* temp, int length) {
int a = temp[1];
int c = 1;
temp[1] = temp[length];
while (c * 2 < length || c * 2 + 1 < length) {
if (c * 2 < length && c * 2 + 1 < length) {//两个子结点都存在
if (temp[2 * c + 1] >= temp[2 * c] && temp[c] > temp[2 * c]) {
int b = temp[c];
temp[c] = temp[2 * c];
temp[2 * c] = b;
c *= 2;
}
else if (temp[2 * c + 1] < temp[2 * c] && temp[c] > temp[2 * c + 1]) {
int b = temp[c];
temp[c] = temp[2 * c + 1];
temp[2 * c + 1] = b;
c = c * 2 + 1;
}
else{
break;
}
}
else if (c * 2 < length && c * 2 + 1 >= length) {//只有左儿子存在
if (temp[c] > temp[2 * c]) {
int b = temp[c];
temp[c] = temp[2 * c];
temp[2 * c] = b;
c *= 2;
}
else {
break;
}
}
else {//都不存在
if (temp[c] > temp[2 * c + 1]) {
int b = temp[c];
temp[c] = temp[2 * c + 1];
temp[2 * c + 1] = b;
c = c * 2 + 1;
}
else {
break;
}
}
}
return a;
}
//堆排序
int* ex5_3_sort6(int* array, int length) {
if (array == NULL) {
cout << "error,ex5_3_sort6" << endl;
return NULL;
}
int temp[200], length_t = 0;//用数组创建堆
for (int i = 0; i < length; i++) {
insert(temp, array[i], length_t);
length_t++;
}
for (int i = 0; i < length; i++) {
array[i] = pop(temp, length_t);
length_t--;
}
return array;
}
时间复杂度为O(nlogn),其中建堆为O(n),出堆为O(nlogn),插入和出堆复杂度为O(logn)
空间复杂度为O(1),为就地排序
为不稳定排序