常用的选择排序算法有两种:直接选择排序和堆排序。
一、直接选择排序
基本思路:
第一趟比较:程序将记录定位在第一个数据上,拿第一个数据依次和后面的数据进行比较,如果第一个数据大于后面的某个数据,交换它们,....依次进行下去。这趟比较将选出最小的数据并将其排在第一位。
第二趟比较:程序将记录定位在第二个数据上,拿第二个数据依次和后面的数据进行比较,如果第二个数据大于后面的某个数据,交换它们,....依次进行下去。这趟比较将选出第二小的数据并将其排在第二位。
......
依此规则进行比较n-1趟,这组数据中第2大的数据被选出之后,被排在倒数第1位,剩下的就是最大的数了。
Java实现代码:
//定义一个数据包装类 public class DataWrap implements Comparable<DataWrap> { int data; String flag; public DataWrap(int data, String flag) { this.data = data; this.flag = flag; } public String toString() { return data + flag; } //根据data实例变量来决定两个DataWrap的大小 public int compareTo(DataWrap dw) { return this.data > dw.data ? 1 : (this.data == dw.data ? 0 : -1); } }
直接选择排序:
//选择排序 public class SelectSort { public static void selectSort(DataWrap[] data) { System.out.println("开始排序"); int arrayLength = data.length; //依次进行n-1趟比较, 第i趟比较将第i大的值选出放在i位置上。 for (int i = 0; i < arrayLength - 1 ; i++ ) { //第i个数据只需和它后面的数据比较 for (int j = i + 1 ; j < arrayLength ; j++ ) { //如果第i位置的数据 > j位置的数据, 交换它们 if (data[i].compareTo(data[j]) > 0) { DataWrap tmp = data[i]; data[i] = data[j]; data[j] = tmp; } } System.out.println(java.util.Arrays.toString(data)); } } public static void main(String[] args) { DataWrap[] data = { new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(16 , ""), new DataWrap(9 , "") }; System.out.println("排序之前:\n" + java.util.Arrays.toString(data)); selectSort(data); System.out.println("排序之后:\n" + java.util.Arrays.toString(data)); } }
下面是排序过程:
排序之前:
[21, 30, 49, 30*, 16, 9]
开始排序
[9, 30, 49, 30*, 21, 16]
[9, 16, 49, 30*, 30, 21]
[9, 16, 21, 49, 30, 30*]
[9, 16, 21, 30, 49, 30*]
[9, 16, 21, 30, 30*, 49]
排序之后:
[9, 16, 21, 30, 30*, 49]
对上面的算法改进,每次找到最小的数据的索引,减少交换的次数,提高算法效率:
public class SelectSort2 { public static void selectSort(DataWrap[] data) { System.out.println("开始排序"); int arrayLength = data.length; //依次进行n-1趟比较, 第i趟比较将第i大的值选出放在i位置上。 for (int i = 0; i < arrayLength - 1 ; i++ ) { //minIndex永远保留本趟比较中最小值的索引 int minIndex = i ; //第i个数据只需和它后面的数据比较 for (int j = i + 1 ; j < arrayLength ; j++ ) { //如果第minIndex位置的数据 > j位置的数据 if (data[minIndex].compareTo(data[j]) > 0) { //将j的值赋给minIndex minIndex = j; } } //每趟比较最多交换一次 if (minIndex != i) { DataWrap tmp = data[i]; data[i] = data[minIndex]; data[minIndex] = tmp; } System.out.println(java.util.Arrays.toString(data)); } } public static void main(String[] args) { DataWrap[] data = { new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(16 , ""), new DataWrap(9 , "") }; System.out.println("排序之前:\n" + java.util.Arrays.toString(data)); selectSort(data); System.out.println("排序之后:\n" + java.util.Arrays.toString(data)); } }
下面是排序过程:
排序之前:
[21, 30, 49, 30*, 16, 9]
开始排序
[9, 30, 49, 30*, 16, 21]
[9, 16, 49, 30*, 30, 21]
[9, 16, 21, 30*, 30, 49] 不交换
[9, 16, 21, 30*, 30, 49] 不交换
[9, 16, 21, 30*, 30, 49]
排序之后:
[9, 16, 21, 30*, 30, 49]
可以看出:直接选择排序的第n趟比较至多交换一次,永远总是拿n-1位的数据和中间某个数据(本趟比较中最小的数据)进行交换。如果本趟比较时第n-1位(本趟比较的第一位)数据已经是最小,则无需进行交换。
C++代码实现:
#include<iostream> using namespace std; //直接选择排序 void select_sort(int a[],int len) { int i,j,x,l; for(i=0;i<len;i++) { x = a[i];//每次遍历前对x和i的初值设定 l = i; for(j=i;j<len;j++)//遍历从i位置向数组尾部进行 { if(a[j] < x) { x = a[j];//x保存每次遍历搜索到的最小数 l = j;//l记录最小数的位置 } } a[l] = a[i];//把最小数与a[i]交换 a[i] = x; } } void main() { int data[9] = {54,38,96,23,15,72,60,45,83}; select_sort(data,9); for(int i=0;i<9;i++) { cout<<data[i]<<" "; } }
二、堆排序
首先,关于堆的概念这里就不说了,详情请看《算法导论》
堆排序的关键在于建堆,下面简单介绍一下建堆的过程:
第1趟将索引0至n-1处的全部数据建大顶(或小顶)堆,就可以选出这组数据的最大值(或最小值)。将该堆的根节点与这组数据的最后一个节点交换,就使的这组数据中最大(最小)值排在了最后。
第2趟将索引0至n-2处的全部数据建大顶(或小顶)堆,就可以选出这组数据的最大值(或最小值)。将该堆的根节点与这组数据的倒数第二个节点交换,就使的这组数据中最大(最小)值排在了倒数第二位。
......
第k趟将索引0至n-k处的全部数据建大顶(或小顶)堆,就可以选出这组数据的最大值(或最小值)。将该堆的根节点与这组数据的倒数第k个节点交换,就使的这组数据中最大(最小)值排在了倒数第k位。
所以我们只是在重复做两件事:
1:建堆
2:拿堆的根节点和最后一个节点交换
下面通过一组数据说明:
9,79,46,30,58,49
1:先将其转换为完全二叉树,如图
2:完全二叉树的最后一个非叶子节点,也就是最后一个节点的父节点。最后一个节点的索引为数组长度len-1,那么最后一个非叶子节点的索引应该是为(len-1)/2.也就是从索引为2的节点开始,如果其子节点的值大于其本身的值。则把他和较大子节点进行交换,即将索引2处节点和索引5处元素交换。交换后的结果如图:
建堆从最后一个非叶子节点开始即可
3:向前处理前一个节点,也就是处理索引为1的节点,此时79>30,79>58,因此无需交换。
4:向前处理前一个节点,也就是处理索引为0的节点,此时9<79,9<49,因此无需交换。应该拿索引为0的节点与索引为1的节点交换,因为79>49.如图
5:如果某个节点和它的某个子节点交换后,该子节点又有子节点,系统还需要再次对该子节点进行判断。如上图因为1处,3处,4处中,1处的值大于3,4出的值,所以还要交换。
Java代码实现:
//堆排序 public class HeapSort { public static void heapSort(DataWrap[] data) { System.out.println("开始排序"); int arrayLength = data.length; //循环建堆 for (int i = 0; i < arrayLength - 1 ; i++ ) { //建堆 builMaxdHeap(data , arrayLength - 1 - i); //交换堆顶和最后一个元素 swap(data , 0 , arrayLength - 1 - i); System.out.println(java.util.Arrays.toString(data)); } } //对data数组从0到lastIndex建大顶堆 private static void builMaxdHeap(DataWrap[] data , int lastIndex) { //从lastIndex处节点(最后一个节点)的父节点开始 for (int i = (lastIndex - 1) / 2 ; i >= 0 ; i--) { //k保存当前正在判断的节点 int k = i; //如果当前k节点的子节点存在 while (k * 2 + 1 <= lastIndex) { //k节点的左子节点的索引 int biggerIndex = 2 * k + 1; //如果biggerIndex小于lastIndex,即biggerIndex + 1 //代表的k节点的右子节点存在 if (biggerIndex < lastIndex) { //如果右子节点的值较大 if(data[biggerIndex].compareTo(data[biggerIndex + 1]) < 0) { //biggerIndex总是记录较大子节点的索引 biggerIndex++; } } //如果k节点的值小于其较大子节点的值 if(data[k].compareTo(data[biggerIndex]) < 0) { //交换它们 swap(data , k , biggerIndex); //将biggerIndex赋给k,开始while循环的下一次循环, //重新保证k节点的值大于其左、右子节点的值。 k = biggerIndex; } else { break; } } } } //交换data数组中i、j两个索引处的元素 private static void swap(DataWrap[] data , int i , int j) { DataWrap tmp = data[i]; data[i] = data[j]; data[j] = tmp; } public static void main(String[] args) { DataWrap[] data = { new DataWrap(21 , ""), new DataWrap(30 , ""), new DataWrap(49 , ""), new DataWrap(30 , "*"), new DataWrap(21 , "*"), new DataWrap(16 , ""), new DataWrap(9 , "") }; System.out.println("排序之前:\n" + java.util.Arrays.toString(data)); heapSort(data); System.out.println("排序之后:\n" + java.util.Arrays.toString(data)); } }
实现过程:
排序之前:
[21, 30, 49, 30*, 21*, 16, 9]
开始排序
[9, 30, 21, 30*, 21*, 16, 49]
[16, 30*, 21, 9, 21*, 30, 49]
[16, 21*, 21, 9, 30*, 30, 49]
[9, 16, 21, 21*, 30*, 30, 49]
[9, 16, 21, 21*, 30*, 30, 49]
[9, 16, 21, 21*, 30*, 30, 49]
排序之后:
[9, 16, 21, 21*, 30*, 30, 49]
C++实现:
#include<iostream> using namespace std; int heapSize = 0; void swap(int array[] , int i , int j) { int temp; temp = array[i]; array[i] = array[j]; array[j] = temp; } //array[index]与其左右子树进行递归对比,用最大值替换array[index],index表示堆顶索引 void MaxHeapify(int array[] , int index ) { int left = 2*index;//左子节点索引 int right = 2*index+1;//右子节点索引 int largest;//最大数 //用largest存储堆顶与其左子节点的较大者 if(left<=heapSize && array[left]>array[index]) largest = left; else largest = index; //用largest存储堆顶与其右子节点的较大者 if(right<=heapSize && array[right]>array[largest]) largest = right; //此时largest为堆顶,左子节点,右子节点最大者 if(largest != index) { //如果堆顶不是最大者,则交换,并递归调整堆 swap(array , index , largest); MaxHeapify(array , largest); } } //初始化堆,将数组中的每一个元素放到合适的位置。此时堆顶的元素为数组的最大值 void BuildMaxHeap(int array[],int length ) { heapSize = length; for(int i=length-1 ; i>=0 ; i--) { MaxHeapify(array , i ); } } //排序 void HeapSort(int array[] , int length) { //初始化堆 BuildMaxHeap(array,length); for(int i=length ; i>=1 ; i--) { swap(array , 0 , i); //堆顶元素array[0](即数组的最大值)被置换到数组的尾部array[i] heapSize--;//从堆中移除该元素 MaxHeapify(array , 0);//重建堆 } } int main() { int a[8] = {68,20,39,88,97,46,59,11}; int i; HeapSort(a,8); for(i=0 ; i<8 ; i++) { cout << a[i] << " "; } cout <<endl; return 0; }
这是经过改正的,终于解决了.....