java数据结构
排序算法
简单排序
在我们的程序中,排序是非常常见的一种需求,提供一些数据元素,把这些数据元素按照一定的规则进行排序。比
如查询一些订单,按照订单的日期进行排序;再比如查询一些商品,按照商品的价格进行排序等等。所以,接下来
我们要学习一些常见的排序算法。
在 java 的开发工具包 jdk 中,已经给我们提供了很多数据结构与算法的实现,比如 List,Set,Map,Math 等等,都
是以 API 的方式提供,这种方式的好处在于一次编写,多处使用。我们借鉴 jdk 的方式,也把算法封装到某个类中,
那如果是这样,在我们写 java 代码之前,就需要先进行 API 的设计,设计好之后,再对这些 API 进行实现。
就比如我们先设计一套 API 如下:
类名 | ArrayList |
---|---|
构造方法 | ArrayList():创建 ArrayList 对象 |
成员方法 | 1. boolean add(E e):向集合中添加元素 2. E remove(int index):从集合中删除指定的元素 |
然后再使用 java 代码去实现它。
冒泡排序
冒泡排序(Bubble Sort), 是一种计算机科学领域的较简单的排序算法。
需求:
排序前:{4,5,6,3,2,1}
排序后:{1,2,3,4,5,6}
排序原理:
- 比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
- 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大
值。
动画演示:
冒泡排序API设计:
类名 | Bubble |
---|---|
构造方法 | Bubble():创建Bubble对象 |
成员方法 |
1.public static void sort(Comparable[] a):对数组内的元素进行排序 2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w 3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值 |
冒泡排序的代码实现:
public class Bubble {
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a){
for(int i=a.length-1;i>0;i--){
for(int j=0;j<i;j++){
//{6,5,4,3,2,1}
//比较索引j和索引j+1处的值
if (greater(a[j],a[j+1])){
exch(a,j,j+1);
}
}
}
}
/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
测试代码:
import java.util.Arrays;
public class BubbleTest {
public static void main(String[] args) {
Integer[] arr = {4,5,6,3,2,1};
Bubble.sort(arr);
System.out.println(Arrays.toString(arr));//{1,2,3,4,5,6}
}
}
冒泡排序使用了双层 for 循环,时间复杂度为 \(\mathcal{O}(n^2)\).
选择排序
选择排序是一种更加简单直观的排序方法。
需求:
排序前:{4,6,8,7,9,2,10,1}
排序后:{1,2,4,5,7,8,9,10}
排序原理:
- 每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处
的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引 - .交换第一个索引处和最小值所在的索引处的值
动画演示:
选择排序API设计:
类名 | Selection |
---|---|
构造方法 | Selection():创建Selection对象 |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素进行排序 2.private static boolean greater(Comparable v,Comparable w): 判断v是否大于w 3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值 |
选择排序的代码实现:
public class Selection {
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a){
for(int i=0;i<=a.length-2;i++){
//定义一个变量,记录最小元素所在的索引,默认为参与选择排序的第一个元素所在的位置
int minIndex = i;
for(int j=i+1;j<a.length;j++){
//需要比较最小索引minIndex处的值和j索引处的值;
if (greater(a[minIndex],a[j])){
minIndex=j;
}
}
//交换最小元素所在索引minIndex处的值和索引i处的值
exch(a,i,minIndex);
}
}
/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
测试代码:
import java.util.Arrays;
public class SelectionTest {
public static void main(String[] args) {
//原始数据
Integer[] a = {4,6,8,7,9,2,10,1};
Selection.sort(a);
System.out.println(Arrays.toString(a));//{1,2,4,5,7,8,9,10}
}
}
选择排序使用了双层 for 循环,时间复杂度为 \(\mathcal{O}(n^2)\).
插入排序
插入排序(Insertion sort)是一种简单直观且稳定的排序算法。
插入排序的工作方式非常像人们排序一手扑克牌一样。开始时,我们的左手为空并且桌子上的牌面朝下。然后,我
们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在
手中的每张牌进行比较,如下图所示:
需求:
排序前:{4,3,2,10,12,1,5,6}
排序后:{1,2,3,4,5,6,10,12}
排序原理:
- 把所有的元素分为两组,已经排序的和未排序的;
- 找到未排序的组中的第一个元素,向已经排序的组中进行插入;
- 倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待
插入元素放到这个位置,其他的元素向后移动一位。
动画演示:
插入排序API设计:
类名 | Insertion |
---|---|
构造方法 | Insertion():创建Insertion对象 |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素进行排序 2.private static boolean greater(Comparable v,Comparable w): 判断v是否大于w 3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值 |
插入排序代码实现:
public class Insertion {
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a){
for(int i=1;i<a.length;i++){
for(int j=i;j>0;j--){
//比较索引j处的值和索引j-1处的值,如果索引j-1处的值比索引j处的值大,则交换数据,如果不大,那么就找到合适的位置了,退出循环即可;
if (greater(a[j-1],a[j])){
exch(a,j-1,j);
}else{
break;
}
}
}
}
/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
测试代码:
import java.util.Arrays;
public class InsertionTest {
public static void main(String[] args) {
Integer[] a = {4,3,2,10,12,1,5,6};
Insertion.sort(a);
System.out.println(Arrays.toString(a));//{1,2,3,4,5,6,10,12}
}
}
插入排序使用了双层 for 循环,时间复杂度为 \(\mathcal{O}(n^2)\).
高级排序
之前我们学习过基础排序,包括冒泡排序,选择排序还有插入排序,并且对他们在最坏情况下的时间复杂度做了分
析,发现都是 \(\mathcal{O}(n^2)\),而平方阶通过我们之前学习算法分析我们知道,随着输入规模的增大,时间成本将急剧上
升,所以这些基本排序方法不能处理更大规模的问题,接下来我们学习一些高级的排序算法,争取降低算法的时间
复杂度最高阶次幂。
希尔排序
希尔排序是插入排序的一种,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本。
前面学习插入排序的时候,我们会发现一个很不友好的事儿,如果已排序的分组元素为{2,5,7,9,10},未排序的分组
元素为{1,8},那么下一个待插入元素为1,我们需要拿着1从后往前,依次和10,9,7,5,2进行交换位置,才能完成真
正的插入,每次交换只能和相邻的元素交换位置。那如果我们要提高效率,直观的想法就是一次交换,能把1放到
更前面的位置,比如一次交换就能把1插到2和5之间,这样一次交换1就向前走了5个位置,可以减少交换的次数,
这样的需求如何实现呢?接下来我们来看看希尔排序的原理。
需求:
排序前:{9,1,2,5,7,4,8,6,3,5}
排序后:{1,2,3,4,5,5,6,7,8,9}
排序原理:
- 选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;
- 对分好组的每一组数据完成插入排序;
- 减小增长量,最小减为1,重复第二步操作。
增长量h的值每一固定的规则,我们这里采用以下规则:
int h=1
while(h<5){
h=2h+1;//3,7
}
//循环结束后我们就可以确定h的最大值;
h的减小规则为:
h=h/2
动画演示:
希尔排序的API设计:
类名 | Shell |
---|---|
构造方法 | Shell():创建Shell对象 |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素进行排序 2.private static boolean greater(Comparable v,Comparable w): 判断v是否大于w 3.private static void exch(Comparable[] a,int i,int j):交换a数组中,索引i和索引j处的值 |
希尔排序的代码实现:
public class Shell {
/*
对数组a中的元素进行排序
*/
public static void sort(Comparable[] a){
//1.根据数组a的长度,确定增长量h的初始值;
int h = 1;
while(h<a.length/2){
h=2*h+1;
}
//2.希尔排序
while(h>=1){
//排序
//2.1.找到待插入的元素
for (int i=h;i<a.length;i++){
//2.2把待插入的元素插入到有序数列中
for (int j=i;j>=h;j-=h){
//待插入的元素是a[j],比较a[j]和a[j-h]
if (greater(a[j-h],a[j])){
//交换元素
exch(a,j-h,j);
}else{
//待插入元素已经找到了合适的位置,结束循环;
break;
}
}
}
//减小h的值
h= h/2;
}
}
/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w){
return v.compareTo(w)>0;
}
/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j){
Comparable temp;
temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
测试代码:
import java.util.Arrays;
public class ShellTest {
public static void main(String[] args) {
Integer[] a = {9,1,2,5,7,4,8,6,3,5};
Shell.sort(a);
System.out.println(Arrays.toString(a));//{1,2,3,4,5,5,6,7,8,9}
}
}