冒泡排序与两数交换的实现与优化
By: 大志若愚
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,故名。
过程图:
(一)标准版本:未优化版本
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 冒泡排序 { class Program { static void Main(string[] args) { int[] array = {9,1,2,3,4,5,6,7,8,0}; array = BubbleSort01(array); foreach (int item in array) { Console.Write(item + " "); } Console.ReadKey(); } /// <summary> /// 从大到小排序 /// </summary> /// <param name="array">要进行冒泡排序的数组</param> /// <returns>排序后的数组</returns> private static int[] BubbleSort01(int[] array) { for (int i = 0; i < array.Length - 1; i++) { for (int j = 0; j < array.Length -1 - i; j++) { if (array[j] < array[j + 1]) { Swap01(ref array[j], ref array[j + 1]); } } } return array; } // 实现二数交换,下面有其他方式的解释
private static void Swap01(ref int num1,ref int num2) { int temp; temp = num1; num1 = num2; num2 = temp; } } }
(2)设置单标志:优化版本一
标志:用于标志每趟循环是否有数进行了交换,若无,则数组已实现排序
private static int[] BubbleSort02(int[] array) { bool flag = true; int n = array.Length - 1; while (flag) { flag = false; for (int i = 0; i < n - 1; i++) { if (array[i] < array[i + 1]) { //Swap01(ref array[i], ref array[i + 1]); //Swap02(ref array[i], ref array[i + 1]); Swap03(ref array[i], ref array[i + 1]); flag = true; } } n--; } return array; }
flag用于标志每趟循环是否有数进行交换[即是否仍乱序],若为true,则标志数组还未有序。
(3)设置双标志:优化版本二
标志一:用于标志每趟循环是否有数进行了交换,若flag=0,则数组已实现排序
标志二:若循环有数进行了交换,标志最后一次交换的位置,标志之后的则已经有序
private static int[] BubbleSort03(int[] array) { int k; int flag = array.Length - 1; while (flag > 0) { k = flag; flag = 0; for (int i = 0; i < k; i++) { if (array[i] < array[i + 1]) { Swap03(ref array[i], ref array[i + 1]); flag = i; } } } return array; }
flag用于标志每趟循环是否有数进行交换[即是否仍乱序],若为大于0,则标志数组还未有序。
增加了k来标志在这之前仍需要排序位置,在k之后的则已经有序。
实现两数交互:
我想在这里说下,实现两数交互的实现
(1)一般做法,利用第三方变量,同时因为int,enum,struct典型的值类型,需要进行引用传值
private static void Swap(ref int num1,ref int num2) { int temp = num1; num1 = num2; num2 = temp; }
不使用第三方变量实现:解方程,异或
(2) 解方程式:[名字可以随意,自己可以取个好记忆的]
private static void Swap(ref int num1, ref int num2) { num1 = num1 - num2; num2 = num1 + num2; num1 = num2 - num1; }
但是这种方式引入了一个陷阱,如果num1是一个很大的正数而num2是一个很大的负数,那么num1-num2就会溢出。虽然在num2=num1+num2时可能会通过再一次溢出从而获得真实的num1的值,不推荐这种利用未定义行为的解法。
如何理解这种解法?其实第一行是num1=num1-num2还是num1=num1+num2再或者是num1=num1*num2都可以,对应地在第二行把num2通过这个式子和num2本身的运算求出num1即可,再在第三行利用num1、num2的组合值以及原先的num1求解num2。明显地,使用*比+或-更容易溢出。理解后,完全不必死记硬背这三个式子,看成是解方程就不难了。
(3)XOR[异或]
private static void Swap03(ref int num1, ref int num2) { num1 = num1 ^ num2; num2 = num2 ^ num1; num1 = num2 ^ num1; }
异或的性质:
(1) X^0 = X 且 X^X = 0
(2)交换律: X^Y = Y^X
(3)结合律: (X^Y)^Z = X^(Y^Z)
(4)自反性: X^Y^Y = X
所以,这个也会出现一个问题:int x = 10; Swap(ref x, int ref x); 就会出现为x = 0的情况,对此我认为是因为由于对x的引用导致的,在交换过程中: num1 = num1 ^ num2;使得num1 = 0;而num2与num1都是对x的引用,Swap过程中,就没有保存临时计算产生的数据以及原数据,所以导致num1、num2都成为了0。
交换两个相等的数据是没问题的,比如 int x = 8; int y = 8; Swap03(ref x ,ref y);完全是没有问题的。
一般交换数据不会自己和自己交换数据,但是有时会出现例外,建议交换数据前进行判断是否相等,相等则比进行交换。
private static void Swap04(ref int num1, ref int num2) { if(num1 != num2) { num1 = num1 ^ num2; num2 = num2 ^ num1; num1 = num2 ^ num1; } }