【Java排序算法】快速排序思想的应用

本文介绍了快速排序算法的应用,包括如何实现快速排序的划分操作、递归与非递归实现,以及单向划分和链表排序。重点讨论了快速排序在解决实际问题中的应用,如查询第K小的元素和计算数组中相邻元素的最小差值,通过算法的特性简化问题解决。

快速排序

在前面,我们了解到了快速排序的思想,详情链接可见:快速排序。其实,快速排序也可以看作是分治策略的一种算法模式,根据快速排序的思想,我们可以实现许多实际中的应用问题。下面,我们来看看这些问题该如何实现。

首先,我们来回顾一个快速排序的实现过程,其中最重要的一步操作就是快速排序中的划分操作。

快速排序的划分操作实现
/**
 * 快速排序的划分过程
 */
public static int Partition(int[] arr, int left, int right) {
    int temp = arr[left];
    while (left < right) {
        while (left < right && arr[right] > temp) {
            right--;
        }
        if (left < right) {
            arr[left] = arr[right];
        }
        while (left < right && arr[left] < temp) {
            left++;
        }
        if (left < right) {
            arr[right] = arr[left];
        }
    }
    arr[left] = temp;
    return left;
}

有了划分操作后,我们就可以进行快速排序了。具体排序思想可以参考链接:快速排序

快速排序的实现
快速排序的递归实现
/**
 * 递归方法实现快速排序
 */
public static void quickSort(int[] arr, int left, int right) {
    if (left < right) {
        int pos = Partition(arr, left, right);
        quickSort(arr, left, pos - 1);
        quickSort(arr, pos + 1, right);
    }
}
快速排序的非递归实现
  • 通过队列实现快速排序。
/**
 * 非递归方法实现快速排序
 */
public static void niceQuickSort(int[] arr, int left, int right) {
    Queue<Integer> qu = new LinkedList<Integer>();
    qu.offer(left);// left , right
    qu.offer(right);
    while (!qu.isEmpty()) {
        left = ((LinkedList<Integer>) qu).pollFirst();
        right = ((LinkedList<Integer>) qu).pollFirst();
        int pos = Partition(arr, left, right);
        if (left < pos - 1) //
        {
            qu.offer(left);
            qu.offer(pos - 1);
        }
        if (pos + 1 < right) {
            qu.offer(pos + 1);
            qu.offer(right);
        }
    }
}

我们都知道,在一般情况下,快速排序经常将最左边的元素作为基准元素进行划分操作,那么是否可以随机指定基准的位置呢。

  • 其实,这种情况是可以实现的,我们只需将通过随机对象出来的下标元素与最左边元素进行替换。此时,最左边元素就相当于随机出来的,我们依然可以进行使用上面的划分函数进行快速排序。
/**
 * 快速排序
 * 随机获取基准值(只需将随机获取到的下标的元素与left元素替换即可)
 */
public static int RandPartition(int[] ar, int left, int right) {
    Random rad = new Random();
    int index = rad.nextInt(right - left + 1) + left;
    int tmp = ar[left];
    ar[left] = ar[index];
    ar[index] = tmp;
    return Partition(ar, left, right);
}

接着,我们可以看到,上面的快速排序算法是使用双向共同进行排序的操作,那么是否可以只使用一边进行快速排序呢。

单向划分实现快速排序
  • 具体划分过程如下:

在这里插入图片描述

  • 代码实现:
/**
 * 单向形式实现划分过程
 */
public static int singlePartition(int[] arr, int left, int right) {
    int tmp = arr[left];
    int i = left, j = i + 1;
    while (j <= right) {
        if (arr[j] <= tmp) {
            i += 1;
            if (i != j) {
                swap(arr, i, j);
            }
        }
        ++j;
    }
    swap(arr, left, i);
    return i;
}

/**
 * 递归方法实现单向快速排序
 */
public static void singleQuickSort(int[] arr, int left, int right) {
    if (left < right) {
        int pos = singlePartition(arr, left, right);
        singleQuickSort(arr, left, pos - 1);
        singleQuickSort(arr, pos + 1, right);
    }
}

通过单向划分数组进行快速排序的思想,我们可以很快联系到单向链表的快速排序方法实现,其原理与上述单向数组排序思想相同。

单向链表的快速排序算法实现
/**
 * 单向实现链表划分
 */
public static ListNode ListPartition(ListNode left, ListNode right) {
    int tmp = left.Value;
    ListNode low = left, h = low.Next;
    while (h != right) {
        if (h.Value <= tmp) {
            low = low.Next;
            if (low != h) {
                swap_list(low, h);
            }
        }
        h = h.Next;
    }
    swap_list(left, low);
    reutrn low;
}

/**
 * 单向实现链表快速排序
 */
public static void ListQuickSort(ListNode left, ListNode right) {
    if (left != right) {
        ListNode pos = ListPartition(left, right);
        ListQuickSort(left, pos);
        ListQuickSort(pos.Next, right);
    }
}

在此,我们再次看到了关于快速排序算法的实现,那么通过快速排序算法,我们究竟可以解决哪些实际问题呢。

快速排序思想的应用
  • 首先,我们来看一个代码。
/**
 * 在集合中获取第一小和第二小的元素
 */
public static void Select2Min(int[] ar) {
    if (ar.length < 2) return;
    int min1 = ar[0] < ar[1] ? ar[0] : ar[1];
    int min2 = ar[0] > ar[1] ? ar[0] : ar[1];
    for (int i = 2; i < ar.length; ++i) {
        if (ar[i] < min1) {
            min2 = min1; //注意将 min1 赋值给 min2 
            min1 = ar[i];
        } else if (ar[i] < min2) {
            min2 = ar[i];
        }
    }
    System.out.println(min1);
    System.out.println(min2);
}
  • 这是用于查询数组中第一小和第二小元素的实现过程,那么该如何查询第K小的元素呢?
  • 在快速排序的算法中,其中最重要的特征之一就是,基准值在划分完成后其位置将不再进行改变,那么通过此特征,我们是否可以实现查询第K小的元素呢?答案当然是肯定的。代码如下:
查询第K小的元素
/**
 * 在集合中获取第K小的元素
 */
public static int Select_K(int[] ar, int left, int right, int k) {
    if (left == right && k == 1) return ar[left];
    int index = Partition(ar, left, right);
    //经过第一次划分
    int pos = index - left + 1;// left ... right
    //若K值小于pos,说明K值在左边,将左边进行递归划分
    if (k <= pos) return Select_K(ar, left, index, k);
    //若K值大于pos,说明K值在右边,将右边进行递归划分
    //需要注意此时K值需要减去pos值
    else return Select_K(ar, index + 1, right, k - pos);
}

/**
 * 在集合中获取第K小的元素
 */
public static int Select_KMin(int[] ar, int k) {
    return Select_K(ar, 0, ar.length - 1, k);
}
计算相邻元素的最小差值
  • 该题目的思路为:我们通过上述的查询第K小的元素,可以简单的查询中处于中间位置的元素大小,接着通过递归算法,我们只需要计算左半部分的最小差值、右半部分的最小差值,以及右半部分最小值与左半部分的最大值的差值,通过比较这三个数值的大小,我们就可以得到整个数组中相邻元素的最小差值。
  • 代码如下:
/**
 * 计算相邻元素的最小差值
 *      整形的最大值
 */
static final int maxint = 0x7fffffff;

/**
 * 计算相邻元素的最小差值
 *      获取左半部分最大值
 */
public static int MaxS1(int[] ar, int left, int right) {
    //通过划分函数,此时传入的index值就是左半部分的最大值
    return ar[right];
}

/**
 * 计算相邻元素的最小差值
 *      获取右半部分最小值
 */
public static int MinS2(int[] ar, int left, int right) {
    int tmp = ar[left];
    for (int i = left + 1; i <= right; ++i) {
        if (tmp > ar[i]) {
            tmp = ar[i];
        }
    }
    return tmp;
}

/**
 * 计算相邻元素的最小差值
 *      获取两个元素中的最小值
 */
public static int Min2d(int a, int b) {
    return a < b ? a : b;
}

/**
 * 计算相邻元素的最小差值
 *      获取三个元素中的最小值
 */
public static int Min3d(int a, int b, int c) {
    return Min2d(Min2d(a, b), c);
}

/**
 * 计算相邻元素的最小差值
 */
public static int Cpair(int[] ar, int left, int right) {
    if (right - left < 1) return maxint;
    int k = (right - left + 1) / 2;
    Select_K(ar, left, right, k); // left...index; index+1 ... right;
    int index = left + k - 1;

    int d1 = Cpair(ar, left, index); //s1;
    int d2 = Cpair(ar, index + 1, right); //s2;
    int maxs1 = MaxS1(ar, left, index);
    int mins2 = MinS2(ar, index + 1, right); //s2
    return Min3d(d1, d2, mins2 - maxs1);
}

通过快速排序算法的特征,我们可以更快速的解决许多实际中的问题,这对我们具有非常大的帮助,因此,当我们在遇到较难的问题时,要经常去考虑使用合适的算法去帮助我们解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值