注:此博客不再更新,所有最新文章将发表在个人独立博客limengting.site。分享技术,记录生活,欢迎大家关注
一、Java实现
1.1 固定基准点的快排实现
快排的基本思路为先用某种方式设置一个基准点key(在此先选取输入数组的第一个数为基准点),再调用最核心的partition()函数分别从数组array的两端扫描数组,设两个指示标志(lo指向起始位置,hi指向末尾),首先从后半部分开始,不断前移hi的位置,如果发现有元素小于key,就将该值覆盖array[lo]的值(即array[lo] = array[hi]),再从前半部分开始扫描,不断后移lo的位置,如果发现有元素大于key,就将该值覆盖array[hi]的值(即array[hi] = array[lo]),如此往复循环,每个元素都存在一个副本然后副本再被新的值覆盖,直到lo≥hi时把key的值放到hi这个位置(即array[hi] = key),一次排序就完成了。之后再采用递归调用quickSort()的方式分别对前半部分和后半部分排序,直到lo≥hi时整个数组排序完成。
完成一次排序切分的partition()函数的实现:
public static int partition(int[] array, int lo, int hi) {
int key = array[lo];
while (lo < hi) {
// 搜索后半部分,直到找到一个小于key的数并用它替换array[lo]
while (array[hi] >= key && lo < hi) {
hi --;
}
array[lo] = array[hi];
// 搜索前半部分,直到找到一个大于key的数并用它替换array[hi]
while (array[lo] <= key && lo < hi) {
lo ++;
}
array[hi] = array[lo];
}
// 退出while循环后,lo == hi
array[lo] = key;
return lo;
}
尾递归的quickSort()函数的实现:
public static void quickSort(int[] array, int lo, int hi) {
// 注意终止条件
if (lo >= hi) return;
middle(array,lo,hi);
int index = partition(array,lo,hi);
quickSort(array,lo,index - 1);
quickSort(array,index + 1,hi);
}
1.2 程序的优化过程
1.2.1 快排基准点选取的优化
固定基准点为第一个数虽然程序设计上易于实现,但是在程序运行时如果遇到特殊情况可能会浪费较多的时间,如第一个数就是整个数组中最小的数时,第一次排序并没有任何元素位置发生改变。故采取一种折中的方法,即将数组中的第一个数、最后一个数和排在中间的数三个数中取一个中间的数作为基准点,用middle()函数实现三数取中操作。
public static void middle(int[] array, int lo, int hi) {
int mid = lo + (hi - lo) / 2;
// 先确保hi是三个值中最大的
if (array[lo] > array[hi])
swap(array,lo,hi);
if (array[mid] > array[hi])
swap(array,mid,hi);
// 再将lo和mid比较,把三个数中的中间数放在array[lo]上
if (array[lo] < array[mid])
swap(array,mid,lo);
}
1.2.2 数组输入方式的优化
(1)从给定数组到不定长度数组的键盘输入
一开始为了测试的方便,使用的是给定数组嵌入到程序中直接对其进行排序,后来程序完善的过程中觉得从键盘输入数据更为合理,这样也能方便地测试不同数据的运行时间,但是Java中如何实现从键盘输入不定长度的整数数组?Java基本数据类型中的数组定义是要先给出数组的长度,故只能想到先用一个String把从键盘输入的数据存储起来,再通过String类中的split()方法将String根据空格切分成String[],最后再将String[]转换成int[]作为参数传递。
public static int[] input() {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
String[] str = s.split(" ");
int[] a = new int[str.length];
for (int i = 0; i < str.length; i ++)
a[i] = Integer.parseInt(str[i]);
return a;
}
(2)再到从文本中读取数据
如果要对不同语言或不同算法的运行时间进行测试,手动输入大量的数据显然是不现实的,这时就要采取从txt文件中读取数据的方法。
public static int[] input() {
int[] a = {};
try {
// 注意要把data.txt与src放在一个目录下,否则要使用绝对路径
Scanner scanner = new Scanner(new File("data.txt"));
a = new int[1000];
int i = 0;
while (scanner.hasNextInt())
a[i++] = scanner.nextInt();
}
catch (IOException e) {System.out.println("not found the file");}
return a;
}
1.3 程序编写中遇到的问题
(1)在程序编写中,遇到了一个很小但是很容易出错的问题:
Java中如何用函数调用的方式实现数组中两个数的交换?这个问题在C/C++中用引用或指针就很容易解决,但在Java中不存在指针变量,无论是基本数据类型还是对象的传递都是值传递,并不能改变原有的变量。于是想要将swap抽象为一个函数,只能把数组和下标作为参数进行传递,达到array[a]和array[b]互换的目的;也可以就用三行赋值语句在middle()函数内部实现swap的功能,只不过这样编写会使得程序较为冗杂。
private static void swap(int[] array, int a, int b) {
int tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
(2)在编写从文本中读取数据的部分时,如果直接使用
Scanner scanner = new Scanner(new File("data.txt"));
而不对异常进行处理的话会报错,故再其基础上进行修改,catch IOException
则报异常“not found the file”
public static int[] input() {
int[] a = {};
try {
// 注意要把data.txt与src放在一个目录下,否则要使用绝对路径
Scanner scanner = new Scanner(new File("data.txt"));
a = new int[1000];
int i = 0;
while (scanner.hasNextInt())
a[i++] = scanner.nextInt();
}
catch (IOException e) {System.out.println("not found the file");}
return a;
}
(3)既然要从文本文件中读取数据作为输入,那么包含许多整数的文本文件如何方便地生成?使用Random类中的nextInt()方法生成1000个随机的[0,10000)的整数,+1则为1000个[1,10000]的随机整数。将此程序的输出结果作为测试用例data.txt文件。
import java.util.Random;
public class RanNum {1
public static void main(String[] args) {
Random rand = new Random();
for (int i = 0; i < 1000; i++) {
System.out.println(rand.nextInt(10000) + 1);
}
}
}
1.4 完整程序展示
(1)输入不定长数组版:
import java.util.*;
public class QuickSort {
public static int partition(int[] array, int lo, int hi) {
int key = array[lo];
while (lo < hi) {
// 搜索后半部分,直到找到一个小于key的数并用它替换array[lo]
while (array[hi] >= key && lo < hi) {
hi--;
}
array[lo] = array[hi];
// 搜索前半部分,直到找到一个大于key的数并用它替换array[hi]
while (array[lo] <= key && lo < hi) {
lo++;
}
array[hi] = array[lo];
}
// 退出while循环后,lo == hi
array[lo] = key;
return lo;
}
public static void middle(int[] array, int lo, int hi) {
int mid = lo + (hi - lo) / 2;
// 先确保hi是三个值中最大的
if (array[lo] > array[hi])
swap(array, lo, hi);
if (array[mid] > array[hi])
swap(array, mid, hi);
// 再将lo和mid比较,把三个数中的中间数放在array[lo]上
if (array[lo] < array[mid])
swap(array, mid, lo);
}
private static void swap(int[] array, int a, int b) {
int tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
public static void quickSort(int[] array, int lo, int hi) {
// 注意终止条件
if (lo >= hi) return;
middle(array, lo, hi);
int index = partition(array, lo, hi);
quickSort(array, lo, index - 1);
quickSort(array, index + 1, hi);
}
public static int[] input() {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
String[] str = s.split(" ");
int[] a = new int[str.length];
for (int i = 0; i < str.length; i ++)
a[i] = Integer.parseInt(str[i]);
return a;
}
public static void main(String[] args) {
int[] array = input();
quickSort(array, 0, array.length - 1);
for (int i : array) {
if (i == array[array.length - 1])
System.out.print(i);
else
System.out.print(i + ",");
}
}
}
(2)从txt文件中读取数据版:
import java.io.File;
import java.io.IOException;
import java.util.*;
public class QuickSort {
public static int partition(int[] array, int lo, int hi) {
int key = array[lo];
while (lo < hi) {
// 搜索后半部分,直到找到一个小于key的数并用它替换array[lo]
while (array[hi] >= key && lo < hi) {
hi--;
}
array[lo] = array[hi];
// 搜索前半部分,直到找到一个大于key的数并用它替换array[hi]
while (array[lo] <= key && lo < hi) {
lo++;
}
array[hi] = array[lo];
}
// 退出while循环后,lo == hi
array[lo] = key;
return lo;
}
public static void middle(int[] array, int lo, int hi) {
int mid = lo + (hi - lo) / 2;
// 先确保hi是三个值中最大的
if (array[lo] > array[hi])
swap(array, lo, hi);
if (array[mid] > array[hi])
swap(array, mid, hi);
// 再将lo和mid比较,把三个数中的中间数放在array[lo]上
if (array[lo] < array[mid])
swap(array, mid, lo);
}
private static void swap(int[] array, int a, int b) {
int tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
public static void quickSort(int[] array, int lo, int hi) {
// 注意终止条件
if (lo >= hi) return;
middle(array, lo, hi);
int index = partition(array, lo, hi);
quickSort(array, lo, index - 1);
quickSort(array, index + 1, hi);
}
public static int[] input() {
int[] a = {};
try {
// 注意要把data.txt与src放在一个目录下,否则要使用绝对路径
Scanner scanner = new Scanner(new File("data.txt"));
a = new int[1000];
int i = 0;
while (scanner.hasNextInt())
a[i++] = scanner.nextInt();
}
catch (IOException e) {System.out.println("not found the file");}
return a;
}
public static void main(String[] args) {
int[] array = input();
quickSort(array, 0, array.length - 1);
for (int i : array) {
if (i == array[array.length - 1])
System.out.print(i);
else
System.out.print(i + ",");
}
}
}
程序输出的结果:
最后一行显示Running Time为110ms
二、 C++语言的快排实现
2.1 优化基准点的快排实现
由于Java是由C++衍生出来的,故两者具有许多相似性。在Java程序的基础上很容易写出基准点三数取中且程序内部给定已知数组的C++程序。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int Partition(int a[], int low, int high)
{
int x = a[high];//将输入数组的最后一个数作为主元,用它来对数组进行划分
int i = low - 1;//i是最后一个小于主元的数的下标
for (int j = low; j < high; j++)//遍历下标由low到high-1的数
{
if (a[j] < x)//如果数小于主元的话就将i向前挪动一个位置,并且交换j和i所分别指向的数
{
int temp;
i++;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
//经历上面的循环之后下标为从low到i(包括i)的数就均为小于x的数了,现在将主元和i+1位置上面的数进行交换
a[high] = a[i + 1];
a[i + 1] = x;
return i + 1;
}
void middle(int array[], int lo, int hi) {
int mid = lo + (hi - lo) / 2;
// 先确保hi是三个值中最大的
if (array[lo] > array[hi])
swap(array[lo], array[hi]);
if (array[mid] > array[hi])
swap(array[mid], array[hi]);
// 再将lo和mid比较,把三个数中的中间数放在array[lo]上
if (array[lo] < array[mid])
swap(array[lo], array[mid]);
}
void QuickSort(int a[], int lo, int hi)
{
if (lo >= hi) return;
middle(a, lo, hi);
int index = Partition(a, lo, hi);
QuickSort(a, lo, index - 1);
QuickSort(a, index + 1, hi);
}
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main() {
int a[10] = { 34,28,98,67,33,122,3445,2,4,7 };
QuickSort(a, 0, 9);
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
2.2 程序编写中遇到的问题
(1) C++中如何获取数组的长度?
int len = end(a) - begin(a);
(2) C++中如何从文件中读取数据?
先写出从文件中读取数据的demo测试通过后将其放入程序中进行整合。
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
std::fstream myfile("C:\\Users\\Sunni\\Desktop\\data.txt", std::ios_base::in);
int a;
int i = 0;
int arr[1000] = {};
while (myfile >> a)
{
arr[i] = a;
i++;
}
for (int i = 0; i < 1000; i++)
{
cout << arr[i] << ",";
}
cout << endl;
return 0;
}
(3) 如何测试程序运行的时间?
开头加上 #include <time.h>
clock_t start = clock();
clock_t ends = clock();
cout << "Running Time : " << (double)(ends - start) / CLOCKS_PER_SEC * 1000<<" ms"<< endl;
2.3 完整程序展示
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <time.h>
using namespace std;
int Partition(int a[], int lo, int hi)
{
int x = a[hi];//将输入数组的最后一个数作为主元,用它来对数组进行划分
int i = lo - 1;//i是最后一个小于主元的数的下标
for (int j = lo; j < hi; j++)//遍历下标由low到high-1的数
{
if (a[j] < x)//如果数小于主元的话就将i向前挪动一个位置,并且交换j和i所分别指向的数
{
int temp;
i++;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
//经历上面的循环之后下标为从low到i(包括i)的数就均为小于x的数了,现在将主元和i+1位置上面的数进行交换
a[hi] = a[i + 1];
a[i + 1] = x;
return i + 1;
}
void middle(int array[], int lo, int hi) {
int mid = lo + (hi - lo) / 2;
// 先确保hi是三个值中最大的
if (array[lo] > array[hi])
swap(array[lo], array[hi]);
if (array[mid] > array[hi])
swap(array[mid], array[hi]);
// 再将lo和mid比较,把三个数中的中间数放在array[lo]上
if (array[lo] < array[mid])
swap(array[lo], array[mid]);
}
void QuickSort(int a[], int lo, int hi)
{
if (lo >= hi) return;
middle(a, lo, hi);
int index = Partition(a, lo, hi);
QuickSort(a, lo, index - 1);
QuickSort(a, index + 1, hi);
}
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int* input()
{
std::fstream myfile("C:\\Users\\Sunni\\Desktop\\data.txt", std::ios_base::in);
int a;
int i = 0;
int arr[1000] = {};
while (myfile >> a)
{
arr[i] = a;
i++;
}
return arr;
}
int main() {
clock_t start = clock();
int* arr = input();
int array[1000];
for (int i = 0; i < end(array) - begin(array); i ++)
{
array[i] = *arr;
arr++;
}
QuickSort(array, 0, end(array) - begin(array) - 1);
for (int i = 0; i < end(array) - begin(array); i++)
printf("%d ", array[i]);
printf("\n");
clock_t ends = clock();
cout << "Running Time : " << (double)(ends - start) / CLOCKS_PER_SEC * 1000<<" ms"<< endl;
return 0;
}
程序运行的结果:
最后一行显示Running Time为41ms
2.3 python语言快排实现
Python果然简洁易编写,也可能是由于已经用两种语言写过了,对快排程序更加熟悉,相比于编写java和C++的快排程序,python的coding用时远短于前两种语言,整个过程无bug让人神清气爽。
代码展示:
# coding=UTF-8
import time
def QuickSort(myList,start,end):
#判断low是否小于high,如果为false,直接返回
if start < end:
i,j = start,end
#设置基准数
base = myList[i]
while i < j:
#如果列表后边的数,比基准数大或相等,则前移一位直到有比基准数小的数出现
while (i < j) and (myList[j] >= base):
j = j - 1
#如找到,则把第j个元素赋值给第个元素i,此时表中i,j个元素相等
myList[i] = myList[j]
#同样的方式比较前半区
while (i < j) and (myList[i] <= base):
i = i + 1
myList[j] = myList[i]
#做完第一轮比较之后,列表被分成了两个半区,并且i=j,需要将这个数设置回base
myList[i] = base
#递归前后半区
QuickSort(myList, start, i - 1)
QuickSort(myList, j + 1, end)
return myList
start =time.clock()
theFile = open("C:\\Users\\Sunni\\Desktop\\data.txt", "r")
myList = []
for val in theFile.read().split():
myList.append(int(val))
theFile.close()
print("Quick Sort: ")
QuickSort(myList,0,len(myList)-1)
print(myList)
end = time.clock()
print('Running time: %s ms'%((end-start)*1000))
程序运行结果: