排序算法
1、 冒泡排序
public class BubbleSort {
/**
* 普通版本
*/
public static void sort(int[] arr){
int tem;
for (int i = 0; i <= arr.length-2; i++) {
for (int j = 0; j <= arr.length-2-i; j++) {
if (arr[j]>arr[j+1]){
tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
}
}
/**
* 优化版本
* 如果在一趟比较中,发现没有进行过交换,则说明序列有效,设置一个flag判断是否进行过交换从而减少不必要的比较
*/
public static void sort2(int[] arr){
int tem;
boolean flag = false;
for (int i = 0; i <= arr.length-2; i++) {
for (int j = 0; j <= arr.length-2-i; j++) {
if (arr[j]>arr[j+1]){
flag = true;
tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
if (!flag)
break;
else
flag = false;
}
}
}
2、选择排序
/**
* * 选择排序__从小到大
*/
public class SelectSort {
public static void sort(int[] arr){
int index_min;
int value_min;
for (int i = 0; i <= arr.length-2 ; i++) {
index_min = i;
value_min = arr[i];
for (int j = i+1; j <= arr.length-1 ; j++) {
if (arr[j] < value_min){
index_min = j;
value_min = arr[j];
}
}
arr[index_min] = arr[i];
arr[i] = value_min;
}
}
}
3、插入排序
/**
* 插入排序--从小到大
*/
public class InsertSort {
public static void sort(int[] arr){
for (int i = 1; i <= arr.length-1 ; i++) {
int cur_value = arr[i]; //记录待插入的数
int cur_index = i; // 以当前待插入数的索引为起点,将前面的比 待插入数 大的的数后移
while (cur_index-1 >= 0 && cur_value < arr[cur_index-1] ){
arr[cur_index] = arr[cur_index-1];
cur_index--;
}
arr[cur_index] = cur_value; //插入到合适的位置
}
}
}
4、希尔排序
希尔提出的一种排序算法,也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
/**
* 希尔排序 -- 从小到大
*/
public class ShellSort {
//对有序序列在插入时采用【交换】法
public static void sort(int[] data) {
for (int i = data.length / 2; i > 0; i = i / 2) { //步长
for (int j = i; j <= data.length - 1; j++) {
for (int k = j; k - i >= 0; k = k - i) {
if (data[k] < data[k - i]) {
int tmp = data[k];
data[k] = data[k - i];
data[k - i] = tmp;
}
}
}
}
}
//对有序序列在插入时采用【移位】法,效果更佳!
public static void sort2(int[] data) {
for (int i = data.length / 2; i > 0; i = i / 2) { //步长
for (int j = i; j <= data.length - 1; j++) {
int insert_value = data[j];
int insert_index = j;
while (insert_index-i >= 0 && insert_value < data[insert_index-i]) {
data[insert_index] = data[insert_index-i];
insert_index = insert_index - i;
}
data[insert_index] = insert_value;
}
}
}
}
5 、快速排序(参考慕课网bobo老师的算法课程)
5.1 以第一个元素作为标定点的快速排序
1: 将第一个元素v作为标定点,从第二个元素开始逐个遍历所有元素 ;
2: 初始 j = l ,i = l + 1 ;
3: 如果 i 索引对应的 e 大于 v ,则只需 i 后移一位 即可;
4: 如果 i 索引对应的 e 小于 v ,则需要 j 后移一位,再交换 j 和 i 索引的数据 ;
5: 最后遍历完所有元素,只需交换 l 和 j 索引的数据即可;
6: 然后重复这个过程sort( l , j - 1 ) 、sort( j+1 , j+1,r )
public class QuickSort1 {
public static void sort(int[] data , int l,int r){ //初始调用 sort(int[] data , 0, data.length-1)
if (l>=r)
return;
int p = partition(data,l,r);
sort(data,l,p-1);
sort(data,p+1,r);
}
private static int partition(int[] data ,int l, int r) {
int v = data[l];
int j = l;
for (int i = l+1; i <= r; i++) {
if (data[i] < v){
j++;
swap(data,i,j); //进行步骤4的交换
}
}
swap(data,l,j); //进行步骤5的交换
return j;
}
//定义一个交换元素的方法
private static void swap(int[] data, int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
}
5.2 随机选取标定点的快速排序
对于一个近乎有序数组,如果选取第一个元素作为标定点,算法时间复杂度会达到O(n2)级别。使用随机标定点,可以解决这个问题。
只需要在原来算法的基础上,在partition方法中生成一个随机标定值,然后 l 和 rand 索引对应的值就行
public class QuickSort2 {
public static void sort(int[] data , int l,int r){ //初始调用 sort(int[] data , 0, data.length-1)
if (l>=r)
return;
int p = partition(data,l,r);
sort(data,l,p-1);
sort(data,p+1,r);
}
private static int partition(int[] data ,int l, int r) {
//****************************************************************
int rand = (int) (Math.random()*(r-l+1)+l);// 生成一个随机标定值
swap(data,l,rand);
//****************************************************************
int v = data[l];
int j = l;
for (int i = l+1; i <= r; i++) {
if (data[i] < v){
j++;
swap(data,i,j); //进行步骤4的交换
}
}
swap(data,l,j); //进行步骤5的交换
return j;
}
//定义一个交换元素的方法
private static void swap(int[] data, int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
}
5.3 双路快速排序
随机标定点虽然解决了有序数组的排序时间复杂度高的问题,但对于几乎所有元素都相等的一个数组,随机标定点是没有任何意义的,此时双路排序算法可以解决这个问题。
左边进行循环找到一个 <v 的元素,右边进行循环找到一个 >v 的元素,进行交换
交换完毕,将i向后移动一位,j 向前移动一位
public class QuickSortTwoWay {
public static void sort(int[] data , int l,int r){ //初始调用 sort(int[] data , 0, data.length-1)
if (l>=r)
return;
int p = partition(data,l,r);
sort(data,l,p-1);
sort(data,p+1,r);
}
private static int partition(int[] data ,int l, int r) {
int rand = (int) (Math.random()*(r-l+1)+l);// 生成一个随机标定值
swap(data,l,rand);
int v = data[l];
int i = l+1;
int j = r;
while (true){
while (i <= r && data[i] < v)
i++;
while (j >= l+1 && data[j] > v)
j--;
if (j < i) //考虑边界情况
break;
swap(data,i,j);
i++;
j--; // 如果现在是 i j 的相邻顺序,经过i++,j--,变成 j i 的相邻顺序
}
swap(data,l,j);
return j;
}
//定义一个交换元素的方法
private static void swap(int[] data, int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
}
关于边界情况的考虑
如果是左边已经找到了一个 > v 的元素,退出来循环
但是右边全部是 >v 的元素,于是j继续移动,直到移动到i的前一位才找到 < v 的元素,也退出循环
还有可能左边没有找到 > v 的元素 ,直到找到 j 或 j 的后一位才找到
而此时如果是 j 的后一位,就说明当前j指向的是一个 < v 的元素,所以下一步 j 的循环判断 j 并不做任何移动
而此时如果是 j 位,就说明当前j指向的是一个 > v 的元素,所以下一步 j 的循环判断 j 会向前移动一次
此时变成 j i 的相邻顺序,但此时已经说明所有的元素都已遍历,且不需要任何的交换,直接break,
而此时的 j 指向的是最 < v 的序列中的最后一个元素,交换 l , j 对应的值即可
5.4 三路快速排序
初始:lt = l ; gt = r+1; i = l+1;
如果当前 i 所指元素等于 v ,则 i 向后移动一位即可
如果当前 i 所指元素小于 v ,则 lt 向后移动一位,交换 lt 和 i 对应的元素,由于交换到 i 位置的值是已知等于v 的,所以 i 向后移动一位
如果当前 i 所指元素大于 v ,则gt 向后移动一位,交换 gt 和 i 对应的元素,由于交换到 i 位置的值是未知的,所以 i 不需要移动,需要下次判断来决定该值合适的位置
当 i > gt 的时候,说明元素已检索完毕,此刻交换 l 和 lt 位置对应的值,把 v 放在合适的位置
此刻该段数组已成功分组
然后执行:
sort(data,l,lt-1);
sort(data,gt,r);
public class QuickSortThreeWay {
public static void sort(int[] data,int l,int r){
if (l >= r)
return;
int lt = l;
int gt = r+1;
int i = l+1;
int rand = (int) (Math.random()*(r-l+1)+l);// 生成一个随机标定值
swap(data,l,rand);
int v = data[l];
while (i < gt){
if ( data[i] == v)
i++;
else if (data[i] < v){
swap(data,i,lt+1);
lt++;
i++;
}else {
swap(data,i,gt-1);
gt--;
}
}
swap(data,l,lt);
sort(data,l,lt-1);
sort(data,gt,r);
}
//定义一个交换元素的方法
private static void swap(int[] data, int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
}
6 、归并排序
public class MergeSort {
public static void sort(int[] data, int l, int r) {
if (l >= r)
return;
int mid = (l + r) / 2;
sort(data, l, mid);
sort(data, mid + 1, r);
merge(data, l, mid, r);
}
private static void merge(int[] data, int l, int mid, int r) {
int[] tmp = Arrays.copyOfRange(data, l, r + 1); //将data数组中 l 到 r 的元素拷贝到一个临时数组中,下面操作的时候直接改变data数组中的值
int l_index = l; //左边索引从 l 开始
int r_index = mid + 1; //右边索引从mid+1开始
for (int i = l; i <= r; i++) {
if (l_index > mid) { //说明左边部分以全部处理完毕
data[i] = tmp[r_index - l];
r_index++;
}
else if (r_index > r) { //说明右边部分以全部处理完毕
data[i] = tmp[l_index - l];
l_index++;
}
else if (tmp[l_index-l] < tmp[r_index-l]) { //左边所指元素 < 右边所指元素
data[i] = tmp[l_index-l];
l_index++;
}
else { //左边所指元素 >= 右边所指元素
data[i] = tmp[r_index-l];
r_index++;
}
}
}
}
7 、基数排序
基数排序属于“分配式排序”,又称桶子法,它是通过键值的各个位的值,将要排序的元素分配到某些桶中,达到排序的作用
基数排序是属于稳定性的排序,基数排序法是效率高的稳定性排序
基数排序是桶排序的扩展
public class RadixSort {
public static void sort(int[] data){
int max = data[0];
for (int i = 1; i < data.length; i++) { //找出位数最长的数
if (data[i] > max)
max = data[i];
}
int max_length = (max+"").length(); //得到最大数的长度
int[][] bucket = new int[10][data.length]; // 定义10个容量为data.length的桶
int[] bucket_size = new int[10]; // 定义一个记录每个桶的存放元素数量的数组
for (int k = 0; k < max_length ; k++) { //根据最大数的位数长度,决定要循环的次数
for (int d = 0; d < data.length; d++) { //循环data数组中的数据
int num = data[d]/(int)Math.pow(10,k)%10; // 得到当前位数的值
bucket[num][bucket_size[num]] = data[d]; // 放入对应的桶中
bucket_size[num]++; // 放入元素对应的桶的存放元素数量加1
}
int index = 0;
for (int b = 0; b < 10 ; b++) { //把从0号桶至九号桶取出所有元素,对应更新data中的元素排布
for (int bs = 0; bs < bucket_size[b] ; bs++) {
data[index] = bucket[b][bs];
index++;
}
bucket_size[b] = 0; // 取出每个桶的元素,对应的存放元素数量应置为0
}
}
}
}