快速排序
快速排序的基本思想就是通过一趟排序将待排序列记录分成独立的两部分,其中一部分记录的是比关键字小的部分,还有一部分是比关键字大的一部分;然后再将这两部分记录继续进行排序,以达到整个序列都是有序的。
动态图部分演示:
单路快速排序
实现:排序传入的是数组范围,起始是0 ~ length-1;我们定义一个 i 角标从第二个元素开始遍历,因为我们默认第一个元素为关键字,但为了避免极端情况,我们将随机找一个值为关键字,然后让其和第一个位置数字交换一下位置;我们只考虑小于的部分,让i遍历,定义一个j表示小于部分,也就是小于部分有几个数字,只要遍历的小于关键字,则让其和j+1进行交换,然后j++,表示小于部分数字增加一个;最后将关键字和j角标的数字也就是小于部分的最后一个数字交换一下;这就完成了一轮排序。一次类推。我们是先左后右递归也就是小于部分,再是不小于部分。
package sort;
import java.util.Arrays;
public class QuickSort01 extends Sort{
public QuickSort01(int[] arr){
super(arr);
}
@Override
public void sort() {
quickSort(0,arr.length-1);
}
private void quickSort(int l, int r) {
if(l>=r){
return;
}
int p = partition(l,r);
quickSort(l,p-1);
quickSort(p+1,r);
}
private int partition(int l, int r) {
swap(l,(int)(Math.random()*(r-l+1)+l));
int v = arr[l];
//l+1~j<v<j+1~i
int j=l;
for(int i=l+1;i<=r;i++){
if(arr[i]<v){
swap(j+1,i);
j++;
}
}
swap(l,j);
return j;
}
}
双路快速排序
双路快速排序它是对单路快速排序的优化,定义连个指针将其分为小于等于和大于等于关键字的两部分;它的代码实现比较简单;开始和单路一样,默认第一个元素为关键字,但为了避免极端情况,我们将随机找一个值为关键字,然后让其和第一个位置数字交换一下位置,假设两个指针为left,right分别从第二个和最后一个开始;也是表示小于等于 ,大于等于的元素个数,只不过对于大于等于是增加一个元素角标是减一;而小于等于是加一;只要遍历的值小于关键字left+1,大于的right减一即可,对于left来说,当值是大于等于则需要让left和right交换一下,对于right来说,当值小于等于关键字则让left和right交换一下;当left>right一轮遍历完,将关键字和right交换一下即完成一轮排序。
package sort;
import java.util.Arrays;
public class QuickSort02 extends Sort{
public QuickSort02(int[] arr){
super(arr);
}
@Override
public void sort() {
quickSort(0,arr.length-1);
}
private void quickSort(int l, int r) {
if(l>=r){
return;
}
int p = partition(l,r);
quickSort(l,p-1);
quickSort(p+1,r);
}
private int partition(int l, int r) {
swap(l,(int)(Math.random()*(r-l+1)+l));
int v = arr[l];
int i = l+1;
int j=r;
while (true){
while(i<=r && arr[i]<v){
i++;
}
while(j>=l+1 && arr[j] > v){
j--;
}
if(i>j) {
break;
}
swap(i, j);
i++;
j--;
}
swap(l,j);
return j;
}
}
三路快速排序
三路快速排序也是对二路排序的一个优化,因为二路将和关键字相等的值分给两边,他们还会向下去遍历排序,无疑是多此一举且效率降低了,所以我们三路快速排序是将其分为三部分,小于等于和大于;这样和关键字相等的值就不需要再去遍历排序了。开始还是默认第一个元素为关键字,但为了避免极端情况,我们将随机找一个值为关键字,然后让其和第一个位置数字交换一下位置;定义lt gt表示小于和大于部分;
定义i从第二个元素开始遍历,lt 其实从关键字开始,gt从arr.length开始;因为刚开始两个部分都没有元素;当遍历的值小于关键字,让lt+1和i换一下,lt加一;i继续往后遍历,若是遍历的值大于关键字,让i和gt-1换一下,gt减一;此时i不往后遍历,因为我们不知道交换的后面的值和关键字的大小关系,所以需要先判断;若是遍历的值和关键字相等那就让i继续往后遍历即可。当i大于等于gt,则一轮遍历完成,将lt对应值和关键交换一下,一轮排序就完成了。接着以此类推,先左quickSort(l,lt)后右quickSort(gt,r);递归结束的条件是传入参数范围若是前面大于等于右边则递归结束;
package sort;
import java.util.Arrays;
public class QuickSort03 extends Sort {
public QuickSort03(int[] arr) {
super(arr);
}
@Override
public void sort() {
quickSort(0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
private void quickSort(int l, int r) {
if (l >= r) {
return;
}
swap(l,(int)(Math.random()*(r-l+1)+l));
int lt = l;
int v =arr[l];
int gt = r+1;
int i=l+1;
while(i<gt){
if(arr[i]<v){
swap(i,lt+1);
lt++;
i++;
}else if(arr[i] > v){
swap(gt-1,i);
gt--;
}else{
i++;
}
}
swap(l,lt);
quickSort(l,lt-1);
quickSort(gt,r);
}
}
基数排序(桶排序)
基数排序介绍
基数排序是按照低位先排序,然后收集;再按照高位排序,再收集;以此类推,直到最高位,有时候有些属性是有优先级顺序的;先按照低优先级排序,再按照高优先级排序,最后的次序就是高优先级的在前,高优先级相同的,低优先级别高的在前;
时间复杂度:O(n+m)
空间复杂度:O(n+m)
稳定性:稳定
动态图演示:
package sort;
import lianbiao.LinkedList;
import java.util.Arrays;
public class RadixSort extends Sort{
public RadixSort(int[] arr){
super(arr);
}
@Override
public void sort() {
//获取最大数的长度;
int radix = getRadix(arr);
//创建桶;
LinkedList<Integer>[] list = new LinkedList[10];
//创建桶中的队列;
for(int i=0;i<list.length;i++){
list[i] = new LinkedList<>();
}
//在每轮的分类和收集数据;
for(int r=1;r<=radix;r++){
//将数据按返回的角标分到对应桶中;
for(int i=0;i<arr.length;i++){
list[getIndex(arr[i],r)].offer(arr[i]);
}
// 将桶中元素赋值给原数组arr;
int index = 0;
for(int i=0;i<list.length;i++){
while(!list[i].isEmpty()){
arr[index++] = list[i].poll();
}
}
}
System.out.println(Arrays.toString(arr));
}
//通过所给值和轮数确定该值在该轮对应桶的下标,也表示对应的位数;1是个位,2是十位;
private int getIndex(int num, int r) {
int ret = 0;
for(int i=1;i<=r;i++){
ret = num % 10;
num /= 10;
}
return ret;
}
//找到数组的最大值,并返回最大值的位数;
private int getRadix(int[] arr) {
int max = arr[0];
for(int i=1;i<arr.length;i++){
if(arr[i]>max){
max = arr[i];
}
}
return (max+"").length();
}
}
代码测试:
public class TestSort {
public static void main(String[] args) {
int[] arr = {5,1,3,18,4,9,17,44,32,82,19,0};
RadixSort radixSort = new RadixSort(arr);
radixSort.sort();
}
}
计数排序
计数排序介绍
计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+m)(其中m是整数的范围),快于任何比较排序算法。 [1] 当然这是一种牺牲空间换取时间的做法,而且当O(m)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)
实现计数排序,我们首先要找到所给数组的最大值和最小值;因为对于创建的数组桶中装的是该数出现的次数,而每个数字和桶的对应下标有如下关系;num = min + index;找最大值是为因为桶的容量是max-min+1;
动态图演示:
package sort;
import java.util.Arrays;
public class CountingSort extends Sort{
public CountingSort(int[] arr){
super(arr);
}
@Override
public void sort() {
//1.寻找最大最小值;
int max = arr[0];
int min = arr[0];
for(int i=0;i<arr.length;i++) {
if (max < arr[i]) {
max = arr[i];
}
if (min > arr[i]) {
min = arr[i];
}
}
//2.确定桶的长度,数据-》桶角标间的关系;
//创建桶(装的是数据出现的次数;
int[] temp = new int[max - min + 1];
//3.遍历数组arr,将数据出现的次数装入桶中;
for(int i=0;i<arr.length;i++){
temp[arr[i] - min]++;
}
//4.遍历桶,按照从左到右的顺序,将每个数字按照出现次数 一次回填arr;
int index = 0;
for(int i=0;i<temp.length;i++){
int count = temp[i];
while(count > 0){
arr[index++] = i + min;
count--;
}
}
System.out.println(Arrays.toString(arr));
}
}
代码测试:
public class TestSort {
public static void main(String[] args) {
int[] arr = {5,1,3,18,4,9,17,44,32,82,19,0};
CountingSort countingSort = new CountingSort(arr);
countingSort.sort();
}
}