1、冒泡排序
*******、算法描述
冒泡排序是一种最简单的交换排序,它通过两两比较相邻记录的关键字,如果逆序,则进行交换,从而使关键字小的记录如气泡一样往上“漂移”(左移),或者使关键字大的记录如石块一样逐渐向下“坠落”(右移),总共需要 n - 1 趟,因为到最后一个关键字肯定不用排序了!
*******、算法实现
int * BubbleSort(int a[],int n){
int temp;
for (int i = 0 ; i < n - 1 ; i++){ //循环 n- 1次
int flag = 0; //判断是否发生交换
for (int j = n - 1; j > i; j--){ //将较小的从后面往前面排,i之前表示已经有序,一趟冒泡排序
if (a[j-1] > a[j]){ //若为逆序,将最小的放在前面,只有a[j-1] > a [j]的时候才交换,因此算法是稳定的
temp = a[j-1]; //每次交换都需要移动元素3次
a[j-1] = a[j];
a[j] = temp;
flag = 1;
}
if (flag == 0 )
return a ; //本趟遍历之后没有发生交换,说明已经有序,直接返回排序地址
}
}
return a;
}
*******、算法分析
(1)、时间复杂度
最好情况:初始序列为正序,只需要一趟排序,而且在排序的过程中进行 n - 1次关键字的比较,但是不移动记录!
最坏情况:初始序列为逆序,需要进行 n - 1 趟排序,总的关键字比较次数为 nn /2,记录移动的次数(每次交换都要移动 3 次), 3 nn/2
所以,在平均情况下,冒泡排序关键字的比较次数和记录移动的次数分别约为 nn/4 和 3nn/4
所以平均时间复杂度为为 o (nn)
所以该算法的最坏和平均时间复杂度都是 o(nn)
但是该算法的时间复杂度是o(n)(当数据元素按照递增序列的时候,时间复杂度使最好的!和直接插入排序是一样的!)
(2)、空间复杂度
冒泡排序只有在两个记录交换位置时需要一个辅助空间 temp 做暂存记录,所以空间复杂度为 o (1)
*******、算法特点
稳定排序
科用于链式存储结构
移动次数较多,算法的平均时间性能比直接插入排序差。当初始记录无序,n较大的时候,此算法不宜采用!
2、快速排序
*******、算法描述
快速排序是由冒泡算法改进而来,因为冒泡算法每次交换两个相邻的记录只能消除一个逆序。如果能通过两个交换,一次性消除多个逆序,则会大大加快排序的速度!所以快速排序算法中的一次交换可以消除多个逆序!
*******、算法步骤
在待排序的 n 个记录任取一个记录(通常选取的是第一个记录)作为枢轴,设其关键字为特定变量 t 。经过一趟排序之后,要将所有关键字小于 t 的记录交换到前面,把所有大于 t 的关键字交换到后面!结果将待排序记录分成两个子表,最后将 t 放在分界处的位置。然后分别对上左、右子表重复上述过程!直至每一个子表只有一个记录的时候,停止!
(1)、选择待排序中的第一个记录作为枢轴,将枢轴记录暂存在数组a【0】的位置,使用 t 保存!附设两个指针,一个是low,指向表的下界,一个是high指向表的上界。初始化 low = 0,high = 数组的表长减1(或者线性表的表长)。
(2)、先从表的最右面的位置使用 high 指针依次向左扫描,找到第一个关键字小于枢轴关键字 t 的记录,则将其移动到 low 所指的的位置。具体操作是,当 low < high 的时候,若 high 所指的关键字大于等于 t ,则向左移指针high(high–);否则将 high 所指记录赋值给low所指的位置,即 a[low]= a[high]!
(3)、然后从先从表的最左面的位置使用 low 指针依次向右扫描(因为在上一步,high移动将第一个关键字小于t的找到并且赋值给了 low 所指的位置 a[low]=a[0],所以相当于 high所指的地方空了,现在需要 low 向右移动找到 大于 t 的关键字将其放到 high 所指的位置),当 low < high的时候,若 low 所指的位置的关键字小于 t ,则直接执行 low ++的操作,否则若是 low 在移动的过程中找到所指的位置的关键字大于 t ,则将其所指位置的关键字赋值给 high 所指的位置,即执行 a[high] = a[low]!
(4)、重复上面的步骤(2)、(3),直到 low = high,此时 low 或者 High 所指的位置即是枢轴 t 在此趟排序中的最终位置,将 t 赋值给 low = high 的位置,原表被分成两个子表!
*******、算法实现
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#define endl '\n'
#define MaxSize 20
using namespace std;
typedef struct{
int *data;
int length;
}QuckSort;
//执行一次划分的函数
int PartTion(QuckSort &Q,int low,int high){ //第一个元素作为枢轴
int t = Q.data[low];
while(low < high){
while(low < high && Q.data[high] >= t) --high; //high--
Q.data[low] = Q.data[high]; //high所指位置的元素比枢轴小的元素调到枢轴的左边
while(low < high && Q.data[low] <= t) ++low; //low++
Q.data[high] = Q.data[low]; //low所指位置的元素比枢轴大的元素调到枢轴的右边
}
Q.data[low] = t; //枢轴元素的最终存放位置位置
return low; //将执行一次low=high的位置返回给下一次递归调用
}
//递归函数
void QuckSortFunction(QuckSort &Q){
int low = 0, high = Q.length-1;
if (low < high){ //low 和 high相等时候,退出递归的
int position = PartTion(Q,low,high); //初始以t为界限划分为两个子表
PartTion(Q,low,position-1); //对左子表实行同样的操作
PartTion(Q,position+1,high);
}
}
//输入数据
void QuckSortElement(QuckSort &Q){
Q.data = (int *) malloc (sizeof(int)*MaxSize);
Q.length = 0;
int elem;
cout<<"\n请输入要执行快速排序的数!结束请输入 -1 :";
cin>>elem;
while(elem != -1){
Q.data[Q.length] = elem;
Q.length++;
cin>>elem;
}
cout<<"\n输入快速排序的数有 "<<Q.length<<" 个:";
int i = 0;
while(i < Q.length){
cout<<Q.data[i]<<" ";
i ++;
}
}
main(){
QuckSort Q;
QuckSortElement(Q);
QuckSortFunction(Q);
cout<<"\n\n快速排序结果如下:";
int i = 0;
while(i < Q.length){
cout<<Q.data[i]<<" ";
i ++;
}
}
*******、算法分析
上面快速排序打印结果的“”递归树如下,该算法的时间和空间复杂度与该递归树的调用层数有关!而递归调用的层数同时又和递归树的高度相同!所以查找一个结点就需要 log2n次!
所以 n 个结点的最小高度是 [log2n]+1(向下取整),最大高度是 n
空间复杂度为递归的层数
时间复杂度为 n递归层数(n 是每一层使用 PartTion函数处理的元素,数量的个数不超过 总的元素的个数)
所以最好的时间复杂度是 o(nlog2n)(和递归树的深度是一致的)
平均时间复杂度是 o(nlog2n)
最好的空间复杂度是 o(log2n)
最差的时间复杂度是 o(nn)(和冒泡算法的时间性能是一样的了,每次比较都需要交换)
最差的空间复杂度是 o(n),也就是递归树是单支树!
*******、算法特点
(1)、记录非顺次的移动导致排序方法是不稳定的!
(2)、排序过程中需要定位表的上界和下界,所以适合采用顺序表实现,很难用于链式存储!
(3)、当 n 较大的时候,在平均情况下快速排序的性能是所有内部排序算法中速度最快的一种,所以其适合初始记录无序,n较大的情况!