快速排序
1. 定义
快速排序是指通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。整个排序过程可以递归进行,以此达到整个数据变成有序序列。
2. 算法步骤
- 从数列中挑出一个元素,称为“基准”(pivot)(这里将最后一位元素作为基准元素)。
- 定义左右两个指针,一个从左往右寻找比基准值大的元素,一个从右往左找比基准值小的元素,找到后交换其位置,直到两个指针相遇,相遇后交换当前 左(右)指针 与 基准位置元素。
- 调整后,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
3.实例分析
数组下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
数组内容 | 5 | 2 | 7 | 3 | 11 | 4 | 8 | 1 | 6 |
现在要对上面这个数组进行快速排序
-
首先我们以最左侧元素 6 作为基准
-
定义左指针left从 0号位置 开始向右找比基准大的元素 右指针right从 8号位置 找比基准小的元素
-
找到后,left指针指向 7,right指针指向 1
-
交换两者位置,交换后数组内容如下所示
数组下标 0 1 2 3 4 5 6 7 8 数组内容 5 2 1 3 11 4 8 7 6 -
左右指针继续寻找,left指针指向 11,right指针指向 4
-
交换两者位置,交换后数组内容如下所示
数组下标 0 1 2 3 4 5 6 7 8 数组内容 5 2 1 3 4 11 8 7 6 -
左右指针继续寻找,left指针指向 11(左指针必须小于右指针),right指针指向 11(左指针必须小于右指针),交换左右指针位置元素(由于指向同一元素,相当于没有交换)
-
由于左右指针已经相遇,接着交换左(右)指针(11) 与 基准位置元素(6),交换后数组内容如下图所示
数组下标 0 1 2 3 4 5 6 7 8 数组内容 5 2 1 3 4 6 8 7 11
接着递归求解以基准元素为分界线比基准元素小的部分[5,2,1,3,4] 与比基准元素大的部分[8,7,11]
对于[5,2,1,3,4] 这一部分
数组下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
数组内容 | 5 | 2 | 1 | 3 | 4 |
-
以最左侧元素 4作为基准
-
定义左指针left从 0号位置 开始向右找比基准大的元素 右指针right从 4号位置 找比基准小的元素
-
找到后,left指针指向 5,right指针指向 3
-
交换两者位置,交换后数组内容如下所示
数组下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
数组内容 | 3 | 2 | 1 | 5 | 4 |
-
左右指针继续寻找,left指针指向 5(左指针必须小于右指针),right指针指向 5(左指针必须小于右指针),交换左右指针位置元素(由于指向同一元素,相当于没有交换)
-
由于左右指针已经相遇,接着交换左(右)指针(5) 与 基准位置元素(4),交换后数组内容如下图所示
数组下标 0 1 2 3 4 数组内容 3 2 1 4 5
将其再次按照基准拆为左右两部分,(右边已经有序,循环一次后将直接退出)递归后结果如下
数组下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
数组内容 | 1 | 2 | 3 | 4 | 5 |
对于[8,7,11] 这一部分
数组下标 | 6 | 7 | 8 |
---|---|---|---|
数组内容 | 8 | 7 | 11 |
-
以最左侧元素 11作为基准
-
left指针指向 11(左指针必须小于右指针),right指针指向 11(左指针必须小于右指针),交换左右指针位置元素(由于指向同一元素,相当于没有交换)
-
交换后数组内容如下图所示
数组下标 6 7 8 数组内容 8 7 11
至此排序结束,结果如下
数组下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
数组内容 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 |
4.代码实现
public static void quick(int[] array)
{
quickHelper(array, 0, array.length - 1);
}
/**
* 快排递归方法
* @param array
* @param left
* @param right
*/
private static void quickHelper(int[] array, int left, int right)
{
if (left >= right)
{
return;
}
//index为整理完毕后,left和right的重合位置
int index = partition(array, left, right);
//递归其左右区间
quickHelper(array, left, index - 1);
quickHelper(array, index + 1, right);
}
/**
* 快排非递归方法
* @param array
*/
public static void quickHelperByLoop(int[] array)
{
//借助栈,模拟实现递归过程
Stack<Integer> stack = new Stack<>();
//先将右侧位置压入栈
stack.push(array.length - 1);//初始右起点
//再将左侧位置压入栈
stack.push(0);//初始左起点
while (!stack.isEmpty())
{
int left = stack.pop();
int right = stack.pop();
//如果左边大于等于右边,当前区间已经排序完成(0个或者1个元素)
if (left >= right)
{
continue;
}
//划分区间
int partition = partition(array, left, right);
//右侧区间
stack.push(right);
stack.push(partition + 1);
//左侧区间
stack.push(partition - 1);
stack.push(left);
}
}
/**
* 找到分界点位置
* @param array
* @param left
* @param right
* @return
*/
private static int partition(int[] array, int left, int right)
{
int base = array[right];
int i = left;
int j = right;
while (i < j)
{
//从左往右找到比基准值大的元素
while (i < j && array[i] <= base)
{
i++;
}
System.out.println("i = " + i);
//从右往左找到比基准值小的元素
while (i < j && array[j] >= base)
{
j--;
}
System.out.println("j = " + j);
//找到后交换位置
swap(array, i, j);
}
System.out.println();
//将分界元素与基准元素交换位置
swap(array, right, i);
return i;
}
/**
* 交换位置
* @param array
* @param i
* @param j
*/
private static void swap(int[] array, int i, int j)
{
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}