1. 题目分析
本次实验目的是掌握选择排序、冒泡排序、归并排序、快速排序、插入排序的算法原理。以及以及比较这四种算法的时间复杂度以及空间复杂度。
2. 选择排序
算法分析:
代码实现:
/**
* select_sort:选择排序
* @author chenxi
* @param arr:待排序的数组
*/
public static void selectSort(int arr[]) {
for(int x=0;x<arr.length-1;x++) //最后一个数不用在自己和自己进行比较了,n-1轮
{
for(int y=x+1;y<arr.length;y++)
{
if(arr[x]>arr[y])
{
int temp=arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
}
}
}
3. 冒泡排序
算法分析:相邻的两个元素进行比较,如果符合条件就换位,这样第一轮,最大的数会在最后面,长度在依次递减
代码实现:
/**
* bubbleSort:冒泡排序算法
* @author chenxi
* @param arr:需要排序的数组
*/
public static void bubbleSort(int arr[]) {
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { // 相邻元素两两对比
int temp = arr[j+1]; // 元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
4. 归并排序
算法分析:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
代码实现:
//归并排序
public static void mergeSort(int arr[]) {
sort(arr,0,arr.length-1);
}
private static void sort(int[] arr,int start,int end){
if(start<end){
int mid = (start+end)/2;
sort(arr,start,mid);//左边归并排序,使得左子序列有序
sort(arr,mid+1,end);//右边归并排序,使得右子序列有序
merge(arr,start,mid,mid+1,end);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int start1,int end1,int start2,int end2){
// 建立辅助数组
int len=end2-start1+1;
int[] temp=new int[len];
int i = start1;//左序列指针
int j = start2;//右序列指针
int t = 0;//临时数组指针
while (i<=end1 && j<=end2){
// 将小的放入辅助数组
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
//若左序列此时还有有剩余的,将左边剩余元素填充进temp中
while(i<=end1){
temp[t++] = arr[i++];
}
//若右序列此时还有有剩余的,将右序列剩余元素填充进temp中
while(j<=end2){
temp[t++] = arr[j++];
}
t=0;
//将temp中的元素全部拷贝到原数组中
while(start1 <= end2){
arr[start1++] = temp[t++];
}
}
5. 快速排序
算法分析:
代码实现:
//快速排序
public static void quickSort(int arr[]) {
sort1(arr, 0, arr.length-1);
}
/**
* 将数组的某一段元素进行划分,小的在左边,大的在右边
*/
public static int divide(int[] a, int start, int end){
//每次都以最右边的元素作为基准值
int base = a[end];
//start一旦等于end,就说明左右两个指针合并到了同一位置,可以结束此轮循环。
while(start < end){
while(start < end && a[start] <= base)
//从左边开始遍历,如果比基准值小,就继续向右走
start++;
//上面的while循环结束时,就说明当前的a[start]的值比基准值大,应与基准值进行交换
if(start < end){
//交换
int temp = a[start];
a[start] = a[end];
a[end] = temp;
//交换后,此时的那个被调换的值也同时调到了正确的位置(基准值右边),因此右边也要同时向前移动一位
end--;
}
while(start < end && a[end] >= base)
//从右边开始遍历,如果比基准值大,就继续向左走
end--;
//上面的while循环结束时,就说明当前的a[end]的值比基准值小,应与基准值进行交换
if(start < end){
//交换
int temp = a[start];
a[start] = a[end];
a[end] = temp;
//交换后,此时的那个被调换的值也同时调到了正确的位置(基准值左边),因此左边也要同时向后移动一位
start++;
}
}
//这里返回start或者end皆可,此时的start和end都为基准值所在的位置
return end;
}
/**
* 排序
*/
public static void sort1(int[] a, int start, int end){
if(start > end){
//如果只有一个元素,就不用再排下去了
return;
}
else{
//如果不止一个元素,继续划分两边递归排序下去
int partition = divide(a, start, end);
sort1(a, start, partition-1);
sort1(a, partition+1, end);
}
}
6. 插入排序
算法分析:将当前元素和左边的元素比较,若当前元素小,就交换两者,也就相当于插入
代码实现:
/**
* insertionSort:插入排序
* @author chenxi
* @param arr
*/
public static void insertionSort(int[] arr) {
int len = arr.length;
for (int i = 1; i < len; i++) {
// j表示当前元素的位置,将其和左边的元素比较,若当前元素小,就交换,也就相当于插入
// 这样当前元素位于j-1处,j--来更新当前元素,j一直左移不能越界,因此应该大于0
for(int j=i; j>0 && arr[j]<arr[j-1];j--){
int temp = arr[j];// 元素交换
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
7. 测试代码以及运行结果
测试代码:
/**
*
* random:用于产生随机样本的函数
* @author chenxi
* @param n:样本规模
* @return
*/
public static ArrayList<int []> random(int n) {
ArrayList<int []> arrs=new ArrayList<>();
Random random=new Random();
for(int i=0;i<20;i++) {
int arr[]=new int[n];
for(int j=0;j<arr.length;j++) {
arr[j]=random.nextInt(100);
}
arrs.add(arr);
}
return arrs;
}
/**
* paixu:封装的排序方法发
* @author chenxi
* @param n:样本规模
* @param key:采用的排序方法
*/
public static void paixu(int n,int key) {
ArrayList<int []> arrs=random(n);
if(key==1) {
//排序开始时获得一次当前系统的毫秒值
long start=System.currentTimeMillis();
for(int i=0;i<arrs.size();i++) {
int arr[]=arrs.get(i);
SortedUtil.selectSort(arr);
}
//排序结束时获得一次当前系统的毫秒值
long end=System.currentTimeMillis();
//计算排序所花的时间
System.out.println("选择排序所花时间:"+(end-start)+"ms");
}
if(key==2) {
long start=System.currentTimeMillis();
for(int i=0;i<arrs.size();i++) {
int arr[]=arrs.get(i);
SortedUtil.bubbleSort(arr);
}
long end=System.currentTimeMillis();
System.out.println("冒泡排序所花时间:"+(end-start)+"ms");
}
if(key==3) {
long start=System.currentTimeMillis();
for(int i=0;i<arrs.size();i++) {
int arr[]=arrs.get(i);
SortedUtil.mergeSort(arr);
}
long end=System.currentTimeMillis();
System.out.println("归并排序所花时间:"+(end-start)+"ms");
}
if(key==4) {
long start=System.currentTimeMillis();
for(int i=0;i<arrs.size();i++) {
int arr[]=arrs.get(i);
SortedUtil.quickSort(arr);
}
long end=System.currentTimeMillis();
System.out.println("快速排序所花时间:"+(end-start)+"ms");
}
if(key==5) {
long start=System.currentTimeMillis();
for(int i=0;i<arrs.size();i++) {
int arr[]=arrs.get(i);
SortedUtil.insertionSort(arr);
}
long end=System.currentTimeMillis();
System.out.println("插入排序所花时间:"+(end-start)+"ms");
}
}
package www.domian;
public class Test {
public static void main(String[] args) {
int j=10;
while(j<=100000) {
System.out.println("数据规模为"+j+"数据样本为20:");
for(int i=1;i<6;i++) {
SortedUtil.paixu(j, i);
}
j*=10;
}
}
}
运行结果:
测试结果一:(不够准确)
测试结果二:(比结果一稍微准确一些)
8. 算法复杂度分析及经验归纳
排序算法时间复杂度比较 | |||||
排序方法 数据规模 | 选择排序 | 冒泡排序 | 归并排序 | 快速排序 | 插入排序 |
10 | 0 | 0 | 0 | 0 | 1 |
100 | 4 | 5 | 1 | 1 | 2 |
1000 | 16 | 43 | 3 | 2 | 38 |
10000 | 858 | 6588 | 75 | 46 | 2283 |
100000 | 110127 | 785963 | 762 | 3339 | 287059 |
上表是对测试结果二进行了整理,由上图表格可以看出随着数据规模的增大,排序算所用的时间越长。数据规模的大小与排序的时间也有一定的关系,如图:
在较大的数据规模下,归并排序和快速排序比其他排序明显要快的多。所以建议在大数据规模下尽量使用归并排序或者快速排序,对于小数据规模建议使用其他数据结构较为简单的排序。
注:以上测试数的结果与电脑配置配置有关,此次实验分别用了两台不同配置的电脑进行测试,数据分析时取了一组较为准确的运行结果,但是会存在误差。