[java数据结构与算法分析]----8种排序算法详细总结理解笔记
主要记录大学所学过的8种常见排序算法,方便以后复习查阅
排序的稳定性
若记录序列中有两个或两个以上关键字相等的记录:在排序前Ri先于Rj(i<j),排序后的记录序列仍然是Ri先于Rj,称排序方法是稳定的,否则是不稳定的。
1.直接插入排序----稳定的排序算法
1.1基本算法思想
将待排序的记录Ri,插入到已排好序的记录表R1, R2 ,…., Ri-1中,得到一个新的、记录数增加1的有序表。 直到所有的记录都插入完为止。
设待排序的记录顺序存放在数组R[1…n]中,在排序的某一时刻,将记录序列分成两部分:
◆ R[1…i-1]:已排好序的有序部分;
◆ R[i…n]:未排好序的无序部分。
在刚开始排序时,R[1]是已经排好序的。
简单的说就是 在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排 好顺序的,现在要把第n 个数插到前面的有序数中,使得这 n个数 也是排好顺序的。如此反复循环,直到全部排好顺序。
1.2 图解例子—代码
如9,4,-2,60,13,7进行直接插入排序
public static void insertSort(int[] list) {
// 打印第一个元素
System.out.format("i = %d: ", 0);
printPart(list, 0, 0);
// 第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列
for (int i = 1; i < list.length; i++) {
int j = 0;
int temp = list[i]; // 取出第i个数,和前i-1个数比较后,插入合适位置
// 因为前i-1个数都是从小到大的有序序列,所以只要当前比较的数(list[j])比temp大,就把这个数后移一位
for (j = i - 1; j >= 0 && temp < list[j]; j--) {
list[j + 1] = list[j];
}
list[j + 1] = temp;
System.out.format("i = %d: ", i);
System.out.println();
printPart(list, 0, i);
}
}
2.0 希尔排序(缩小增量法)------不稳定的排序算法
希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种加强版。
2.1基本算法思想
把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。
2.2 图解例子—代码
增量序列是5,3,1,,希尔排序的过程如图所示。
public static void shellSort(int[] list) {
int gap = list.length / 2;
//进行判断,一个数就不用排序
while (1 <= gap) {
// 把距离为 gap 的元素编为一个组,扫描所有组
for (int i = gap; i < list.length; i++) {
int j = 0;
int temp = list[i];
// 对距离为 gap 的元素组进行排序
for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) {
list[j + gap] = list[j];
}
list[j + gap] = temp;
}
System.out.format("gap = %d: ", gap);
for (int n :
list) {
System.out.print(n+" ");
}
gap = gap / 2; // 减小增量
}
}
3.0简单选择排序-------不稳定的排序算法
选择排序(Selection Sort)的基本思想是:每次从当前待排序的记录中选取关键字最小的记录表,然后与待排序的记录序列中的第一个记录进行交换,直到整个记录序列有序为止。
3.1基本算法思想
简单选择排序(Simple Selection Sort ,又称为直接选择排序)的基本操作是:通过n-i次关键字间的比较,从n-i+1个记录中选取关键字最小的记录,然后和第i个记录进行交换,i=1,2, … n-1 。
简单的就是每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾或首位,直到全部排序结束为止。
图解
/**
* 简单选择排序
* @param array 待排数据
*/
public void selectSort(int[] a) {
int length = a.length;
for (int i = 0; i < length; i++) {//循环次数
int key = a[i];
int position=i;
for (int j = i + 1; j < length; j++) {//选出最小的值和位置
if (a[j] < key) {
key = a[j];
position = j;
}
}
a[position]=a[i];//交换位置
a[i]=key;
}
}
4.0堆排序----不稳定的排序算法
4.1堆的定义
顺序存储的完全二叉树
每个结点的关键字都不大于其孩子结点的关键字,这样的堆称为小根堆。
其中每个结点的关键字都不小于其孩子结点的关键字,这样的堆称为大根堆。
举例来说,对于 n 个元素的序列 {R0, R1, … , Rn} 当且仅当满足下列关系之一时,称之为堆:
- Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
- Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)
其中 i=1,2,…,n/2 向下取整;
堆排序是一树形选择排序,它的特点是,在排序过程中,将R[1…n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。
4.2基本算法思想
- 对一组待排序的记录,按堆的定义建立堆(最大堆);交换 R[0]和 R[n];
- 然后,将 R[0…n-1]调整为堆,交换 R[0]和 R[n-1];
- 如此反复,直到交换了 R[0]和 R[1]为止。
以上思想可归纳为两个操作:
- 根据初始数组去构造初始堆(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。
- 每次交换第一个和最后一个元素,输出最后一个元素(最大值),然后把剩下元素重新调整为大根堆。
4.3图解实例及代码理解分析
1 3 4 5 2 6 9 7 8 0
public class HeapAdjust {
public static void HeapAdjust(int[] array, int parent, int length) {
int child = 2 * parent + 1; // 先获得左孩子的下标
//如果有右孩子结点,则进行循环
while (child < length) {
// 如果右孩子结点的值大于左孩子结点,则选取右孩子结点,child + 1 < length判断是否越界
if (child + 1 < length && array[child] < array[child + 1]) {
child++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (array[parent] >= array[child])
break;
// 把孩子结点的值赋给父结点
array[child] = array[parent]^array[child];
array[parent] = array[child]^array[parent];
array[child]= array[parent]^array[child];
// 选取孩子结点的左孩子结点,继续向下筛选
parent = child;
child = 2 * child + 1;
}
}
public static void heapSort(int[] list) {
// 循环建立初始堆
for (int i = list.length / 2; i >= 0; i--) {
HeapAdjust(list, i, list.length);
}
// 进行n-1次循环,完成排序
for (int i = list.length - 1; i > 0; i--) {
// 最后一个元素和第一元素进行交换
list[i] = list[i]^list[0];
list[0] = list[i]^list[0];
list[i] = list[0]^list[i];
// 筛选 R[0] 结点,得到i-1个结点的堆
HeapAdjust(list, 0, i);
System.out.format("第 %d 趟: ", list.length - i);
Arrays.stream(list).forEach(s-> System.out.print(s+" "));
System.out.println();
}
}
public static void main(String[] args) {
int[] array = {6,8,7,9,0,1,3,2,4,5};
heapSort(array);
}
}
[外链图片转存失败(img-FK8UwkKl-1565064633673)(C:\Users\dell-pc\Desktop\笔记md\1565058073416.png)]
5.0冒泡排序-----稳定排序算法
5.1基本算法思想
设待排序记录为(K1,K2,…,Kn)。冒泡排序的基本思想是:从K1开始,依次比较两个相邻的记录Ki和Ki+1(i=1,2,…,n-1) 。若Ki>Ki+1,则交换相应记录ki和ki+1的位置;否则,不进行交换。经过这样一遍处理后,使关键字最大的记录如冒泡一样逐步“漂浮”至“水面”,关键字最大的记录移到了第n个位置上。然后,对前面的n-1个记录进行第2遍排序,重复上述处理过程。第2遍之后,前n-1个记录中关键字最大的记录移到了第n-1个位置上。继续进行下去,直到经过n-1遍为止完成升序排序操作。
简单的说就是在要排序的一组数中,对当前还未排好序的范围内的全部数,重复地自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。
即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。 也因此叫冒泡才行
5.2例子
如下以5、1、6、9、7这5个数据为例进行排序,同时从图中也看出弊端,因此在下面的代码演示中,采用了标志位。不会造成比较次数的多余。
代码实现
public class bubbleSort {
public static void bubbleSort(int[] num) {
int length = num.length;
int i, j;
int temp = 0;
int compareSum = 0;
boolean change = true;/*置交换标志*/
for (i = 0; i < length - 1 && change; ++i)/*最多做n-1遍*/ {
change = false; /*清除交换标志*/
for (j = 0; j < length - 1 - i; ++j)
// 如果逆序则交换记录
if (num[j] > num[j + 1]) {
/* num[0]为暂存单元 */
temp = num[j + 1];
num[j + 1] = num[j];
num[j] = temp;
change = true;
compareSum++;
}
}
System.out.println("比较次数" + compareSum);
}
public static void main(String[] args) {
int[] num = {5, 1, 6, 9, 7};
System.out.print("未比较:");
Arrays.stream(num).forEach(s -> System.out.print(s + ","));
bubbleSort(num);
System.out.print("比较:");
Arrays.stream(num).forEach(s -> System.out.print(s + ","));
}
}
6.0快速排序------不稳定的排序算法
6.1基本思想
选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
一趟快速排序方法:
从序列的两端交替扫描各个记录,将关键字小于基准关键字的记录依次放置到序列的前边;而将关键字大于基准关键字的记录从序列的最后端起,依次放置到序列的后边,直到扫描完所有的记录。
6.2 图解及代码实现
public class QKSort {
public int division(int[] list, int left, int right) {
// 以最左边的数(left)为基准元素
int base = list[left];
while (left < right) {
// 从序列右端开始,向左遍历,直到找到小于base的数
while (left < right && list[right] >= base)
//向左
right--;
// 找到了比base小的元素,将这个元素放到最左边的位置
list[left] = list[right];
// 从序列左端开始,向右遍历,直到找到大于base的数
while (left < right && list[left] <= base)
left++;
// 找到了比base大的元素,将这个元素放到最右边的位置
list[right] = list[left];
}
// 最后将base放到left位置。此时,left位置的左侧数值应该都比left小;
// 而left位置的右侧数值应该都比left大。
list[left] = base;
//基准元素的下标
return left;
}
private void quickSort(int[] list, int left, int right) {
// 左下标一定小于右下标,否则就越界了
if (left < right) {
// 对数组进行分割,取出下次分割的基准标号
int base = division(list, left, right);
System.out.format("base = %d: ", list[base]);
// printPart(list, left, right);
Arrays.stream(list).forEach(s-> System.out.print(s+" "));
// 对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
quickSort(list, left, base - 1);
// 对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
quickSort(list, base + 1, right);
}
System.out.println();
}
@Test
public void TestQkSort(){
int[] array ={60,50,48,37,10,90,84,36};
quickSort(array,0,array.length-1);
}
}
快速排序与冒泡
冒泡每一轮只移动一个元素到一端
7.0 归并排序-------稳定
归并(Merging) :是指将两个或两个以上的有序序列合并成一个有序序列。在内部排序中,通常采用的是2路归并排序。即:将两个位置相邻的记录有序子序列。归并为一个记录的有序序列。
开始归并时,每个记录是长度为1的有序子序列,对这些有序子序列逐趟归并,每一趟归并后有序子序列的长度均扩大一倍;当有序子序列的长度与整个记录序列长度相等时,整个记录序列就成为有序序列。
public class mergingSort {
/* 在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。
这两个有序序列段分别为 array[low, mid] 和 array[mid+1, high]。
先将他们合并到一个局部的暂存数组tempArray 中,带合并完成后再将 tempArray 复制回 array 中。
为了方便描述,我们称 tempArray[low, mid] 第一段,tempArray[mid+1, high] 为第二段。
每次从两个段中取出一个记录进行关键字的比较,将较小者放入 tempArray 中。最后将各段中余下的部分直接复制到 tempArray 中。
经过这样的过程,tempArray 已经是一个有序的序列,再将其复制回 array 中,一次合并排序就完成了。*/
public void Merge(int[] array, int low, int mid, int high) {
int first = low; // 第一段序列
int second = mid + 1; // 是第二段序列
int temp = 0; // 是临时存放合并序列的下标
int[] tempArray = new int[high - low + 1]; // 临时合并后序列
// 扫描第一段和第二段序列,直到有一个扫描结束
while (first <= mid && second <= high) {
// 判断第一段和第二段取出的数哪个更小,将其存入合并序列,并继续向下扫描
if (array[first] <= array[second]) {
tempArray[temp] = array[first];
first++;
temp++;
} else {
tempArray[temp] = array[second];
second++;
temp++;
}
}
// 若第一段序列还没扫描完,将其全部复制到合并序列
while (first <= mid) {
tempArray[temp] = array[first];
first++;
temp++;
}
// 若第二段序列还没扫描完,将其全部复制到合并序列
while (second <= high) {
tempArray[temp] = array[second];
second++;
temp++;
}
// 将合并序列复制到原始序列中
for (temp = 0, first = low; first <= high; first++, temp++) {
array[first] = tempArray[temp];
}
}
/**
* 进行相邻归并
* @param array
* @param gap
* @param length
*/
public void MergePass(int[] array, int gap, int length) {
int i = 0;
// 归并gap长度的两个相邻子表
for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
Merge(array, i, i + gap - 1, i + 2 * gap - 1);
}
// 余下两个子表,后者长度小于gap
if (i + gap - 1 < length) {
Merge(array, i, i + gap - 1, length - 1);
}
}
public int[] sort(int[] list) {
for (int gap = 1; gap < list.length; gap = 2 * gap) {
MergePass(list, gap, list.length);
System.out.print("gap = " + gap + ": ");
Arrays.stream(list).forEach(s-> System.out.print(s+ " "));
}
return list;
}
}
8.0基数排序------稳定
8.1基本思想:
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。 它的各个位数上的基数都是以 0~9 来表示的。也就可以把 0~9 视为 10 个桶。我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 10,个位数上是 0,将这个数存入编号为 0 的桶中。
8.2图解
设有关键字序列为19,20, 0, 82, 66, 18的一组记录,采用基数排序的过程如下图8所示
位数值 | 关键字 | 关键字 |
---|---|---|
0 | 20 /00 | 00 |
1 | /18 | / 19 |
2 | 82 / 20 | |
3 | ||
4 | ||
5 | ||
6 | 66 /66 | |
7 | ||
8 | 18 / 82 | |
9 | 19 |
个位的排序后:20,0,82,66,18,19
十位排序后:00 ,18,19,20,66,82
public class radixsSort {
public static void main(String[] args) {
int a[]={19,20, 0, 82, 66, 18};
radixSort(a);
System.out.print("基数排序:");
for(int i=0;i<a.length;i++){
System.out.format("%s ",a[i]);
}
}
public static void radixSort(int[] array){
//1.首先确定排序的趟数;
// 假设第一个数为为大值,通过确定最大的数来找最大的位数,就是放置桶的趟数
int max=array[0];
for(int i=1;i<array.length;i++){
if(array[i]>max){
max=array[i];
}
}
int time=0;
//判断位数;
while(max>0){
max/=10;
time++;
}
//建立10个队列; 就是10个桶 0-9十个数
List<ArrayList> queue=new ArrayList<ArrayList>();
for(int i=0;i<10;i++){
ArrayList<Integer>queue1=new ArrayList<Integer>();
queue.add(queue1);
}
//外循环 进行time 次分配和收集;放置time次桶
for(int i=0;i<time;i++){
//从0-9依次进行 分配数组元素;
for(int j=0;j<array.length;j++){
//得到数字的第time+1 位数; 函数是求x的y次方 取出位值
int x=array[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10, i);
ArrayList<Integer> queue2=queue.get(x);
queue2.add(array[j]);
queue.set(x, queue2);
}
int count=0;//元素计数器;
//收集队列元素;
for(int k=0;k<10;k++){
while(queue.get(k).size()>0){
ArrayList<Integer>queue3=queue.get(k);
array[count]=queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
}
ArrayList<Integer> queue2=queue.get(x);
queue2.add(array[j]);
queue.set(x, queue2);
}
int count=0;//元素计数器;
//收集队列元素;
for(int k=0;k<10;k++){
while(queue.get(k).size()>0){
ArrayList<Integer>queue3=queue.get(k);
array[count]=queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
}