排序算法(基于Java)的介绍与实现
1、排序算法的介绍 排序也称排序算法,排序是将一位数组,依照指定的顺序进行排列的过程。
2.排序算法的分类
3、算法复杂度
算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度。)
各排序的时间复杂度如图:
一、冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
冒泡排序算法的原理如下: (1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2)对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
对arr{126,80,98,158,204}进行冒泡排序
冒泡排序的稳定性:
冒泡排序就是把小的元素往前调或者把大的元素往后调。 比较是相邻的两个元素比较,
交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的; 如果两个
相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来, 这时候也不会交换,
所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
实现如下:
public class Test1 {
public static void main(String[] args) {
int[] arr = {126,80,98,158,204};
bucketSort(arr);
System.out.println(Arrays.toString(arr));
}
//冒泡排序
public static void bubbleSort(int[] arr)
{
int temp = 0;//定义一个临时变量
boolean flag = false;//定义一个标识变量,判断是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
//比较两个值,如果前面的数比后面的数大,则交换
if (arr[i] > arr[j])
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
flag = true;//表明交换过
}
//代码的优化
if (flag) flag = false;//当交换过,则把标识改为false
else break;//若没交换,则此时数组排序完毕,退出循环。
}
}
}
二、选择排序
选择排序(Selection sort)是一种简单直观的排序算法。 它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
选择排序的实现思想: 第一次从arr[0] - arr[n-1]中找到最小值,然后与arr[0]交换 第二次从arr[1] - arr[n-1]中找到最小值,然后与arr[1]交换 … 第n次从arr[n] - arr[n-1]中找到最小值,然后与arr[n]交换。
对arr{126,80,98,158,204}进行选择排序:
初始: 126 80 98 158 204
第一次: **80** 126 98 158 204
第二次: 80 **98** 126 158 204
第三次: 80 98 **126** 158 204
第四次:80 98 126 **158** 204
第五次:80 98 126 158 **204**
算法实现如下:
public class Test1 {
public static void main(String[] args) {
int[] arr = {126,80,98,158,204};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
//选择排序
public static void selectSort(int[] arr)
{
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;//初始化最小索引为i
int min = arr[i];//初始化最小值为arr[i]
for (int j = i + 1; j < arr.length; j++) {
//找出每轮的最小值
if (arr[j] < min)
{
min = arr[j];
minIndex = j;
}
}
//将最小值放在最arr[i]上交换
if (minIndex != i)
{
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
三、插入排序:
插入排序(InsertSort),一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法,其时间复杂度为O(n2) 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
插入排序法思想: 插入排序(InsertSort)的基本思想是:把n个待排序的元素开成一个有序表和一个无序表,开始时有序表只包含一个元素,无序表中包含n-1元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
初始状态: 128 178 4 82 14 22
第一次插入: (128 178) 4 82 14 22
第二次插入: (4 128 178) 82 14 22
第三次插入: (4 82 128 178) 14 22
第四次插入: (4 14 82 128 178) 22
第五次插入: (4 14 22 82 128 178)
public class Test1 {
public static void main(String[] args) {
int[] arr = {128,178,4,82,14,22 };
System.out.println(Arrays.toString(arr));
}
//插入排序
public static void insertSort(int[] arr)
{
int insertVal = 0;//初始化插入值
int insertIndex = 0;//初始化插入索引
for (int i = 1; i < arr.length; i++) {//
insertVal = arr[i];//定义准备插入的数值
insertIndex = i-1;//arr[i]前面数值的下标
//insertIndex要保证不越界,insertVal<arr[insertIndex]还没找到插入位置
//继续查找,则可以将arr[insertIndex]后移
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//判断是否需要赋值
if (insertIndex + 1 != i)
{
arr[insertIndex + 1] = insertVal;
}
}
}
四、希尔排序
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort), 是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法,其时间复杂度为O(n^(1.3—2))。
希尔排序的基本思想: 希尔排序是把记录按下标的一定数据进行分组,对分好的每组使用插入排序算法进行排序。随着增量逐渐减少,每组的数据越来越多,当增量减到1的时候,整个数组恰好分为1组,此时退出算法。
图示:
代码:
public class Test1 {
public static void main(String[] args) {
int[] arr = {8,9,1,7,2,3,5,4,6,0};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
//希尔排序
public static void shellSort(int[] arr)
{
int temp;
//每次分组比较,每次分组根据arr.length/2
for (int gap = arr.length/2; gap >0 ; gap/=2) {
for (int i = gap; i < arr.length; i++) {
//遍历每个组的元素,增量为gap
for (int j = i - gap; j >= 0 ; j-=gap) {
//如果当前元素大于后面加上步长的元素,则交换
if (arr[j] > arr[i])
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
}
}
五、快速排序
快速排序(QuickSort)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
排序的流程:
(1)首先设置一个临界值(一般设置为中间值)
(2)通过该值将数组分成左右两部分。此时小于临界值的数据放在临界值的左边,大于临界值的数据放在临界值的右边。在临界值左右两边寻找第i次比临界值小的数与第i次比临界值大的数,然后交换。
(3)左右两边的数据接着取临界值,重复(1)(2)步骤
(4)当递归重复完上述过程后,整个数组的排序也完成了。
对arr{-9,78,0,23,-567,70}进行快速排序
public class Test1 {
public static void main(String[] args) {
int[] arr = {-9,78,0,23,-567,70};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
//快速排序
public static void quickSort(int[] arr,int left,int right) {
int l = left; //初始化左索引
int r = right;//初始化右索引
int temp;//临时存储变量
int pivot = (arr[(left+right)/2]);//中轴值
while (l<r) {
//寻找左边需要交换的索引
while (arr[l] < pivot) {
l++;
}
//寻找右边需要交换的索引
while (arr[r] > pivot) {
r--;
}
//此时已经排好序,无需交换
if (l >= r) break;
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//交换完后判断arr[l] == pivot arr[r] == pivot,则需l++,r--
if (arr[l] == pivot) r--;
if (arr[r] == pivot) l++;
}
//如果左索引等于右索引,需要l++,r--
if (l == r) {
l++;
r--;
}
//向左递归
if (left < r)
quickSort(arr,left,r);
//向右递归
if (right > l )
quickSort(arr,l,right);
}
}
**
六、归并排序
**
归并排序(MergeSort)是利用归并的思想来实现排序方法,采用了经典的分治法,分治法将 问题分成一些小的模块再进行递归求解,而治阶段则将分的阶段得到的各种值补在一起,即为 先分而治。其时间复杂度为:o(n log n)
通俗来讲:将需要排序的数组递归拆分成最小模块,然后在治递归合并的时候每次进行排序。
例子:
分:
9 3 4 8 5 7 1 6
9 3 4 8 5 7 1 6
9 3 4 8 5 7 1 6
9 3 4 8 5 7 1 6
治:
3 9 4 8 5 7 1 6
3 4 8 9 1 5 6 7
1 3 4 5 6 7 8 9
##代码:
public class Test1 {
public static void main(String[] args) {
int[] arr = {9,3,4,8,5,7,1,6};
mergeSort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
//归并排序,分加合的方法
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if(left<right){
int mid = (left +right)/2;
mergeSort(arr,left,mid,temp);
mergeSort(arr,mid+1,right,temp);
merge(arr,left,mid,right,temp);
}
}
//归并排序,合的方法
public static void merge(int[] arr,int left,int mid,int right,int[] temp) {
int l= left;//初始化i,左边序列初始的索引
int r = mid + 1;//初始化j,右边序列初始的索引
int t = 0;//temp数组的索引
//先把左右两边(有序)的数组按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while(l<=mid && r<=right)
{
if(arr[l]<=arr[r]) temp[t++] = arr[l++];
else temp[t++] = arr[r++];
}
while (l<=mid) temp[t++] = arr[l++];
while (r<=right) temp[t++] = arr[r++];
//将temp数组拷到arr上
t = 0;
for (int i = left; i <= right; i++) arr[i] = temp[t++];
}
七、基数排序(桶排序)
基数排序的介绍:
(1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义, 它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。
(2)基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m)。
(3)基数排序法是效率高的稳定性排序法,是桶排序的扩展。
(4)其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
基数排序的实现原理为:
将所有待比较的值统一同样的数位长度,数位较短的值则在前面补0,然后,从最低位开始,逐次进行一 一排序。这样从最低位一直到最高位排序完成后,数列则会编程一个有序的序列。下面以图文来讲解基数排序。
首先将数组的最大值求出来,判断需要进行多少轮排序,
对数组arr{128,178,4,82,14,22}使用基数排序,升序排序:
第一轮(将每个数组的个位数取出,分别放在对应的桶子里面):
第二轮(将每个数组的十位数取出,分别放在对应的桶子里面):
第三轮(将每个数组的十位数取出,分别放在对应的桶子里面):
以下为参考代码:
public class Test1 {
public static void main(String[] args) {
int[] arr = {128,178,4,82,14,22};
bucketSort(arr);
System.out.println(Arrays.toString(arr));
}
//基数排序
public static void bucketSort(int[] arr) {
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (max<arr[i]) max = arr[i];
}
int maxLength = (max + "").length();
//创建一个桶
int[][] bucket = new int[10][arr.length];
//定义每个桶里面含有多少个值
int[] bucketElementCounts = new int[10];
//把值放入到桶里面去
for (int m = 0,n = 1; m < maxLength; m++,n*=10) {
for (int j = 0; j < arr.length; j++) {
int digitOfElement = arr[j]/n%10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement] ++;
}
//把每个桶的值取出来
int index = 0;
for (int k = 0; k < bucketElementCounts.length; k++) {
if (bucketElementCounts[k] != 0){
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index++] = bucket[k][l];
}
}
bucketElementCounts[k] = 0;
}
}