常见的五类排序算法图解和实现(交换类:冒泡排序,递归的快速排序)
冒泡排序算法:
总的来说就是两两交换,反复直到有序,第一个记录和第二个记录,若逆序则交换,然后比较第二个和第三个记录,以此类推,直到第 n 个记录和第 n-1个记录比较完毕为止,第一趟排序,结果关键字最大的记录被安排在最后一个位置。对前 n-1个记录继续冒泡排序,使得关键字次大的记录安排在第 n-1个位置。如此重复,直到没有需要交换的记录为止(仅仅是第一个和第二个交换过为止)。整个一趟趟的选出最值的过程,仿佛是那水里的气泡,咕嘟咕嘟的往上翻的过程。
递增冒泡排序过程图解:
一般先比较第一个元素和第二个元素,49大于38,则交换之,且需要提前取出元素49到临时存放处
移动元素,实现两个数的交换
继续两两比较,49小于65,无需交换,65小于97,无需交换,比较97和76,交换,97和13比较交换,97和27比较交换,97和49比较交换。
第一趟排序完毕,把最大的97元素冒泡到了最后。接下来继续第二趟排序,每次都是如此往复。每次都得到本趟次排序的最值。
代码如下;
1 //递增冒泡排序算法实现 2 void bubbleSort(int list[], int len) 3 { 4 int i = len; 5 int j = 0; 6 int k = 0; 7 //排序终止的条件 8 while (i > 1) { 9 //每趟排序的过程 10 for (j = 0; j < i; j++) { 11 //核心交换算法 12 if (list[j] > list[j + 1]) { 13 list[j] = list[j] + list[j + 1]; 14 list[j + 1] = list[j] - list[j + 1]; 15 list[j] = list[j] - list[j + 1]; 16 //交换完毕,最值移动到了末位,需要记录下最新的位置 17 k = j; 18 }//end of if 19 }//end of for 20 //更新每趟排序过程中的 i 值 21 i = k; 22 }//end of while 23 } 24 25 int main(void) 26 { 27 int source[8] = {49, 38, 65, 97, 76, 13, 27, 49}; 28 29 bubbleSort(source, 8); 30 31 for (int i = 0; i < 8; i++) { 32 printf(" %d ", source[i]); 33 } 34 35 return 0; 36 }
13 27 38 49 49 65 76 97 Program ended with exit code: 0
一般情况下每经过一趟“起泡”,“ i 减 1”,但并不是每趟都如此。
平均时间复杂度:T(n) = O(n2)
快速排序算法
基本思想:任选一个记录,以它的关键字作为“枢轴”,凡关键字小于枢轴的记录均移至枢轴之前,凡关键字大于枢轴的记录均移至枢轴之后。(整个过程是交替扫描和交换的过程),这个记录开始选取的时候,一般选取第一个记录作为枢轴。做法是附设两个指针 low 和 high,从 high 所指位置起向前搜索找到第一个关键字小于枢轴的关键字的记录与枢轴记录交换,然后从 low 所指位置起向后搜索找到第一个关键字大于枢轴的关键字的记录与枢轴记录交换,重复这两步直至 low = high 为止。
递增排序的图解如下:
初始状态,第一趟排序,一般第一个记录为枢轴,这里是52
因为是递增排序,high 往前扫描,找第一个和52比较,小于52的记录,即23
把枢轴52临时存储在 temp 处,把23交换到枢轴之前
high 扫描交换之后,马上换到 low,low 从前面开始扫描,找到第一个和52比较,大于52的记录,即80。
交换
转到 high,扫描,交换14
到 low,扫描,没有发现比52大的,直到 low 和 high 重合
最后把 temp 里的枢轴记录,存放到重合的位置即可
这样就完成了一趟快速排序,注意是一趟而已。
一趟快速排序代码如下:
1 //一次划分的快速排序算法 2 int partitionSort(int l[], int low, int high) 3 { 4 //第一次快速排序,一般把第一个记录看成是枢轴记录,临时存放 5 int temp = l[low]; 6 //设置枢轴记录 7 int pivot = l[low]; 8 //扫描和比较终止的条件low=high 9 while (low < high) { 10 //先从 high 开始向前扫描第一个比枢轴记录小的记录,交换 11 while (low < high && l[high] > pivot) { 12 //找不到就继续扫描 13 high--; 14 } 15 //找到了,把比枢轴记录小的,交换到低端 16 l[low] = l[high]; 17 //循环交替扫描,从 low 开始向后扫描找第一个比枢轴大的记录,交换 18 while (low < high && l[low] <= pivot) { 19 //找不到就继续扫描 20 low++; 21 } 22 //找到了,把比枢轴记录大的,交换到高端 23 l[high] = l[low]; 24 }//end of while 25 //一次排序终止,把枢轴记录放到终止位置 26 l[low] = temp; 27 //返回枢轴所在位置 28 return low;//返回high也可以 29 } 30 31 int main(void) 32 { 33 int source[10] = {52, 49, 80, 36, 14, 58, 61, 97, 23, 75}; 34 35 partitionSort(source, 0, 9); 36 37 for (int i = 0; i < 10; i++) { 38 printf(" %d ", source[i]); 39 } 40 41 return 0; 42 }
23 49 14 36 52 58 61 97 80 75 Program ended with exit code: 0
继续探究快速排序算法
首先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列“递归”进行一趟快速排序。序列越是无序,快速排序的效果越好。
这里显然有递归的思想在里面,完整代码如下
1 //一次划分的快速排序算法 2 int partitionSort(int l[], int low, int high) 3 { 4 //第一次快速排序,一般把第一个记录看成是枢轴记录,临时存放 5 int temp = l[low]; 6 //设置枢轴记录 7 int pivot = l[low]; 8 //扫描和比较终止的条件low=high 9 while (low < high) { 10 //先从 high 开始向前扫描第一个比枢轴记录小的记录,交换 11 while (low < high && l[high] > pivot) { 12 //找不到就继续扫描 13 high--; 14 } 15 //找到了,把比枢轴记录小的,交换到低端 16 l[low] = l[high]; 17 //循环交替扫描,从 low 开始向后扫描找第一个比枢轴大的记录,交换 18 while (low < high && l[low] <= pivot) { 19 //找不到就继续扫描 20 low++; 21 } 22 //找到了,把比枢轴记录大的,交换到高端 23 l[high] = l[low]; 24 }//end of while 25 //一次排序终止,把枢轴记录放到终止位置 26 l[low] = temp; 27 //返回枢轴所在位置 28 return low;//返回high也可以 29 } 30 31 //递归的思想完成完整的快速排序算法 32 void quickSort(int l[], int low, int high) 33 { 34 //对长度大于1的顺序表进行快速排序,先进行一次分割的划分 35 if (low < high) { 36 int pivot = partitionSort(l, low, high); 37 //递归的分别对子序列进行一次划分快速排序的过程 38 quickSort(l, low, pivot - 1); 39 quickSort(l, pivot + 1, high); 40 } 41 } 42 43 int main(void) 44 { 45 int source[10] = {52, 49, 80, 36, 14, 58, 61, 97, 23, 75}; 46 47 quickSort(source, 0, 9); 48 49 for (int i = 0; i < 10; i++) { 50 printf(" %d ", source[i]); 51 } 52 53 return 0; 54 }
注意:第一次调用函数 quickSort时,待排序记录序列的上、下界分别为 1 和 L.length。
快速排序算法时间复杂度分析
假设一次划分所得枢轴位置 i=k,则对 n 个记录进行快排所需时间:
T(n) = Tpass(n) + T(k-1) + T(n-k),其中 Tpass(n) 为对 n 个记录进行一次划分所需时间。
若待排序列中记录的关键字是随机分布的,则 k 取 1 至 n 中任一值的可能性相同。由此可得快速排序所需时间的平均值为:
设 Tavg(1)≤b,则可得结果:
快速排序时间复杂度结论:
快速排序的时间复杂度为 O(n log2(n))。注意:快速排序的一次划分所需时间和表长成正比,且到目前为止快速排序是平均速度最大的一种排序方法。排序趟数和初试序列有关。
快速排序的空间复杂度
O(log2(n))
快速排序的蜕化特点:
在最坏的情况,即待排序序列已经按关键字“正序”排列的情况下,每次划分只得到一个比上一次少一个对象的子序列。这样,必须经过 n-1 趟才能把所有对象定位,而且第 i 趟需要经过 n-i 次关键码比较才能找到第 i 个对象的安放位置,快速排序将蜕化为起泡排序,其最坏的时间复杂度为 O(n2),所以快速排序适用于原始记录排列杂乱无章的情况。
快速排序退化的例子
用第一个对象作为基准对象
改进方法:
为避免出现快速排序蜕化为冒泡排序的情况,需在进行一次划分之前,进行“预处理”
即:先对 L(s), L(t) 和 L[(s+t)/2],其中开始的时候,s=0,t=length,进行相互比较,然后取关键字的大小为中间的记录为枢轴记录。