一、一维数组快速排序
解决思路
- 对一维数组进行多次划分,使得每次划分结束后,返回下标左边的数都小于该位置的数,右边的都大于该数。(划分函数实现)然后多次划分之后数组有序。
- 对经过一次划分后,划分点左半部分和右半部分进行再划分,不断重复此过程直到所划分区间内只剩一个数时结束,此时数组有序。(快排函数实现)
- 用下面这组数据进行举例:
int []ar = {56,78,12,34,90,67,100,45,23,89};
实现步骤
划分函数:
- 先随机拿出一个数标记为 tmp(这里取0位置的数),再定义两个指针 i 和 j ,分别从数组的两端向中间查找。
- 从 j 位置开始判断, j 位置的数 > tmp时,j 向左移动一位,直到 j 位置的数 < tmp,将 j 位置的数放到 i 位置;
- 当 i 位置的数 < tmp,i 向右移动一位,直到 i 位置的数 > tmp,将 i 位置的数放到 j 位置;
- 循环进行上面 2,3 步的操作,直到 i 和 j 指向同一位置,将 tmp 放入该位置,一次划分结束。
快排函数:
用 pos 标记每次划分后返回的下标,对 pos 左右两边分别调用划分函数(过程与上面图示部分类似),运用递归进行多次划分,区间内只剩一个数时结束。
代码实现
//普通划分
public static int Parition(int []br,int left,int right){
int i = left,j = right;
int tmp = br[i];
while (i<j){
while ( i<j && br[j] > tmp){
--j;
}
if (i < j){
br[i] = br[j];
}
while (i<j && br[i] <= tmp){
++i;
}
if (i < j){
br[j] = br[i];
}
}
br[i] = tmp;
return i;
}
//交换函数
private static void Swap_br(int[] br, int i, int j) {
int tmp = br[i];
br[i] = br[j];
br[j] = tmp;
}
//单向划分
public static int Parition1(int []br,int left,int right){
int i = left,j = i+1;
int tmp = br[i];
while (j <= right){
if(br[j] <= tmp){
i += 1;
Swap_br(br,i,j);
}
++j;
}
Swap_br(br,left,i);
return i;
}
//随机划分
public static int RandParition(int []br,int left,int right){
Random random = new Random();
int index = random.nextInt((right - left+1))+left;//注意点
int tmp = br[left];
br[left] = br[index];
br[index] = tmp;
return Parition(br,left,right);
}
//快速排序递归实现
private static void QuickPass(int[] br, int left, int right) {
if(left < right){
int pos = Parition(br,left,right);
QuickPass(br,left,pos-1);
QuickPass(br,pos+1,right);
}
}
//快速排序非递归实现(应用栈 或 队列)
private static void QuickPass1(int[] br, int left, int right) {
Stack<Integer> st = new Stack<>();
if(left >= right){
return;
}
st.push(left);
st.push(right);//入栈
while(!st.isEmpty()){
left = st.pop();
right = st.pop();
int pos = Parition1(br, left, right);
if(left < pos-1){
st.push(left);
st.push(pos-1);
}
if(right > pos+1){
st.push(pos+1);
st.push(right);
}
}
}
//打印
public static void Print_Ar(int []br){
for (int i=0;i<br.length;++i){
System.out.print(br[i] + " ");
}
System.out.println();
}
//测试
public static void main(String[] args) {
int []ar = {56,78,12,34,90,67,100,45,23,89};
Print_Ar(ar);//原数组
QuickPass(ar,0,ar.length-1);
Print_Ar(ar);//排序后数组
}
二、单链表快速排序
解决思路
- 单链表排序和一维数组排序类似,唯一不同的就是划分方式不同。由于单链表只有 next域,没有 prev域,因此只能从头向尾查找,进行单向划分。
- 将0位置元素的值标记为 tmp,定义两个指针 ip,jp分别从前两个元素从头向尾走。
- 如果 jp 位置的数 <= tmp,ip走一步,将 ip 和 jp 位置的元素进行交换,若 jp 位置元素 > tmp,jp走一步,直到 链表末尾元素判断完,0位置和 ip位置元素交换,划分结束。最终效果与上面一样,划分点左边的元素都比当前位置元素小,右边的元素都比它大。
实现步骤
划分过程如下:
代码实现
class LinkList{
static class ListNode{
int data;
ListNode next;
//构造函数
public ListNode() {
data = 0;
next = null;
}
public ListNode(int x){
data = x;
next = null;
}
public ListNode(int x,ListNode narg){
data = x;
next = narg;
}
}
ListNode head;
public LinkList(){
head = null;
}
//链表初始化
void Init_List(int []br){
head = new ListNode(br[0]);
ListNode p = head;
for(int i=1;i<br.length;++i){
ListNode s = new ListNode(br[i]);
p.next = s;
p = p.next;
}
}
//交换函数
public void Swap_Node(ListNode first, ListNode second) {
int tmp = first.data;
first.data = second.data;
second.data = tmp;
}
//划分函数
public ListNode Parition(ListNode left,ListNode right){
ListNode ip = left;
ListNode jp = ip.next;
int tmp = left.data;
while (jp != right){
if(jp.data <= tmp){
ip = ip.next;
Swap_Node(ip,jp);
}
jp = jp.next;
}
Swap_Node(left,ip);
return ip;
}
//快速排序(递归实现)
public void QuickPass(ListNode left,ListNode right){
if(left != right){
ListNode pos = Parition(left,right);
QuickPass(left,pos);
QuickPass(pos.next,right);
}
}
//快速排序(非递归实现)
public void NiceQuickSort(){
if(head == null || head.next == null){
return;
}
Queue<ListNode> q = new LinkedList<>();
q.offer(head);
q.offer(null);
while (!q.isEmpty()){
ListNode left = q.poll();
ListNode right = q.poll();
ListNode p = Parition(left,right);
if(left != p){
q.offer(left);
q.offer(p);
}
if(p.next != right){
q.offer(p.next);
q.offer(right);
}
}
}
//打印
public void Print_List(){
ListNode p = head;
while (p != null){
System.out.print(p.data + " ");
p = p.next;
}
System.out.println();
}
}
public class TestDemo4_18_2 {
public static void main(String[] args) {
int []ar = {56,78,12,34,90,67,100,45,23,89};
LinkList myt = new LinkList();
myt.Init_List(ar);
myt.Print_List();
myt.QuickPass(myt.head, null);
myt.Print_List();
}
}
三、应用快速排序找数组前两大的数
解决思路
- 先将前两个数进行比较,大的赋值给max1,小的赋值给max2。然后将其他数据与这两个值进行比较,不断更新max1和max2。
- 从第三个数开始向后遍历,比max1大,当前位置的值变为max1,第一大变为第二大(原max1变为max2);比max2大,当前位置的值变为max2。
实现
//找出前两大数
private static void Print_2Max(int[] ar) {
int max1 = (ar[0]>ar[1]) ? ar[0] : ar[1];
int max2 = (ar[0]>ar[1]) ? ar[1] : ar[0];
for(int i = 2;i<ar.length;++i){
if(ar[i] > max1){
max2 = max1;
max1 = ar[i];
}else if(ar[i] >max2){
max2 = ar[i];
}
}
System.out.printf("max1 : %d max2 : %d \n",max1,max2);
}
public static void main(String[] args) {
int []ar = {56,78,12,34,90,67,100,45,23,89};
Print_2Max(ar);
}
四、应用快速排序查找数组中第k小的数
解决思路
- 先对数组进行划分和一次快速排序,再在数组中进行查找。
- 一次排序后,返回下标元素左边的元素都比它小,右边的数都比它大,将该下标标记为 index。由于数组下标从0开始,所以 index左边(包括自身)应该有 index-left+1(left为当前划分区间最左边元素的下标)个元素,将 index-left+1 标记为 pos。
- 若 k 比pos小或者等于 pos,则要找的元素在pos左边,递归pos左半部分,比 pos大则递归右半部分。
实现
//快速排序查找
private static int Print_K(int[] ar, int left, int right,int k) {
if(left == right && k == 1){
return ar[left];
}
int index = Parition(ar,left,right);
int pos = index - left + 1;
if(k <= pos){
return Print_K(ar,left,index,k);
}else {
return Print_K(ar,index+1,right,k-pos);
}
}
//交换函数
private static void Swap_br(int[] br, int i, int j) {
int tmp = br[i];
br[i] = br[j];
br[j] = tmp;
}
//划分函数
public static int Parition(int []br,int left,int right){
int i = left,j = i+1;
int tmp = br[i];
while (j <= right){
if(br[j] <= tmp){
i += 1;
Swap_br(br,i,j);
}
++j;
}
Swap_br(br,left,i);
return i;
}
//打印
public static void Print_K_Min(int k,int []ar){
System.out.printf("%d => %d \n",k,Print_K(ar,0,ar.length-1,k));
}
//测试
public static void main(String[] args) {
int []ar = {56,78,12,34,90,67,100,45,23,89};
Print_2Max(ar);
Print_K_Min(3,ar);
}