给出一个初始序列,分别用归并排序和快速排序两种分治法将所给序列变换为有序序列,翰出结果,输出时要求有文字说明。
方法一:
1.归并排序算法实现:
1.将序列不断划分为左右两个子序列,直到每个子序列只有一个元素。
2.将相邻的两个子序列合并为一个有序序列,不断合并直到整个序列有序。
实现:
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid); //将左半部分进行排序
mergeSort(arr, mid + 1, right); //将右半部分进行排序
merge(arr, left, mid, right); //将两个有序子数组合并操作
}
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[arr.length]; //临时数组用于存储归并后的序列
int i = left; //左序列指针
int j = mid + 1; //右序列指针
int k = left; //临时数组指针
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int x = left; x <= right; x++) {
arr[x] = temp[x];
}
}
2.快速排序算法实现:
1.选择一个元素作为基准值,将序列分为左右两部分,使得左边的元素都小于基准值,右边的元素都大于基准值。
2.对左右两部分分别进行递归操作,直到序列有序。
实现:
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivot = partition(arr, left, right); //基准值所在的位置
quickSort(arr, left, pivot - 1); //对左半部分进行快速排序
quickSort(arr, pivot + 1, right); //对右半部分进行快速排序
}
}
public static int partition(int[] arr, int left, int right) {
int pivot = arr[left]; //选择左边第一个元素作为基准值
int i = left;
int j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) { //从右往左找小于基准值的元素
j--;
}
if (i < j) {
arr[i++] = arr[j];
}
while (i < j && arr[i] < pivot) { //从左往右找大于基准值的元素
i++;
}
if (i < j) {
arr[j--] = arr[i];
}
}
arr[i] = pivot; //最后将基准值插入到合适的位置
return i;
}
算法复杂度分析:
归并排序和快速排序的平均时间复杂度都是O(nlogn),其中归并排序的空间复杂度为O(n),而快速排序的空间复杂度为O(logn)。在最坏情况下,快速排序的时间复杂度可能会退化到O(n^2),而归并排序的时间复杂度不会受到影响。因此,当需要稳定的排序结果并且空间充足时,通常选择归并排序。而当需要快速排序结果并且空间有限时,可以选择快速排序。
方法二:
1.归并排序:
public class MergeSort {
public void sort(int[] arr) {
mergeSort(arr, 0, arr.length - 1);
}
private void mergeSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int m = 0; m < temp.length; m++) {
arr[left + m] = temp[m];
}
}
}
2.快速排序:
public class QuickSort {
public void sort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
private void quickSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int pivot = partition(arr, left, right);
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 1, right);
}
private int partition(int[] arr, int left, int right) {
int pivot = arr[left];
int i = left;
int j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) {
j--;
}
if (i < j) {
arr[i] = arr[j];
}
while (i < j && arr[i] <= pivot) {
i++;
}
if (i < j) {
arr[j] = arr[i];
}
}
arr[i] = pivot;
return i;
}
}
上述代码中,归并排序和快速排序的时间复杂度均为O(nlogn)。但是在实际运行中,快速排序的常数项相对较小,因此比归并排序的效率更高。
以下是用于测试的代码:
public class Main {
public static void main(String[] args) {
int[] arr = {9, 5, 7, 4, 2, 8, 1, 6, 3};
MergeSort mergeSort = new MergeSort();
QuickSort quickSort = new QuickSort();
mergeSort.sort(arr);
System.out.println("归并排序结果:");
printArr(arr);
quickSort.sort(arr);
System.out.println("快速排序结果:");
printArr(arr);
}
private static void printArr(int[] arr) {
for (int value : arr) {
System.out.print(value + " ");
}
System.out.println();
}
}
输出结果如下:
归并排序结果:
1 2 3 4 5 6 7 8 9
快速排序结果:
1 2 3 4 5 6 7 8 9
可以看出,归并排序和快速排序均能够正确地将原始序列变换为有序序列。快速排序的效率比归并排序更高。
方法三:
1.归并排序伪代码:
mergeSort(arr[], l, r)
if l < r
middle = (l+r) / 2
mergeSort(arr, l, middle) // 递归左侧子数组
mergeSort(arr, middle+1, r) // 递归右侧子数组
merge(arr, l, middle, r) // 合并两个有序子数组
merge(arr[], l, middle, r)
n1 = middle - l + 1 // 左侧子数组长度
n2 = r - middle // 右侧子数组长度
创建两个临时数组 L[] 和 R[]
for i = 1 to n1
L[i] = arr[l+i-1] // 拷贝左侧子数组到 L[]
for j = 1 to n2
R[j] = arr[middle+j] // 拷贝右侧子数组到 R[]
i = 1, j = 1, k = l
while i <= n1 and j <= n2
if L[i] <= R[j]
arr[k] = L[i]
i++
else
arr[k] = R[j]
j++
k++
// 将剩余元素拷贝到 arr[]
while i <= n1
arr[k] = L[i]
i++, k++
while j <= n2
arr[k] = R[j]
j++, k++
2.快速排序伪代码:
quickSort(arr[], low, high)
if low < high
pi = partition(arr, low, high) // 获取 pivot 的位置
quickSort(arr, low, pi-1) // 对左侧子数组递归排序
quickSort(arr, pi+1, high) // 对右侧子数组递归排序
partition(arr[], low, high)
pivot = arr[high] // 定义 pivot 的值
i = low - 1 // 初始化索引 i 为 low-1
for j = low to high-1
// 如果当前元素小于等于 pivot
if arr[j] <= pivot
i++ // 移动索引 i
swap(arr[i], arr[j])
swap(arr[i+1], arr[high])
return i+1
Java 代码实现:
1.归并排序:
public class MergeSort {
public static void sort(int[] arr) {
mergeSort(arr, 0, arr.length-1);
}
private static void mergeSort(int[] arr, int l, int r) {
if (l < r) {
int m = (l + r) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m+1, r);
merge(arr, l, m, r);
}
}
private static void merge(int[] arr, int l, int m, int r) {
int n1 = m - l + 1;
int n2 = r - m;
int[] L = new int[n1];
int[] R = new int[n2];
for (int i = 0; i < n1; i++)
L[i] = arr[l+i];
for (int j = 0; j < n2; j++)
R[j] = arr[m+1+j];
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
}
2.快速排序:
public class QuickSort {
public static void sort(int[] arr) {
quickSort(arr, 0, arr.length-1);
}
private static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi-1);
quickSort(arr, pi+1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i+1, high);
return i+1;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度分析:
归并排序有一个瓶颈:合并两个子数组需要 O(n) 的时间。而在递归调用时,每个数组都会不断地被拆分,因此递归树的深度最多为 O(log n)。因此,归并排序的时间复杂度是 O(n log n)。
对于快速排序,最坏情况下每次将 pivot 放到了最后,这时 partition 函数只能将数组分成一个元素和其余元素两部分,因此递归树的高度达到了 O(n)。平均情况下,递归树的高度近似于 O(log n)。因此,快速排序的时间复杂度为 O(n^2) 和 O(n log n) 之间,取决于 pivot 的选择方式和数据的分布情况。
与计算机运行统计时间的对比分析:
我们可以使用 System.currentTimeMillis() 或 System.nanoTime() 计算程序的运行时间。下面是一个简单的示例,将排序函数分别应用于一个长度为 100,000 的随机整数数组:
public class Main {
public static void main(String[] args) {
int size = 100000;
int[] arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = (int) (Math.random() * size);
}
long start = System.currentTimeMillis();
MergeSort.sort(arr);
// QuickSort.sort(arr);
long end = System.currentTimeMillis();
System.out.println("Execution time: " + (end - start) + "ms");
}
}
使用归并排序时,输出结果为 Execution time: 65ms。
使用快速排序时,输出结果为 Execution time: 8ms。
这是因为在本例中,快速排序的平均时间复杂度更低,而且 pivot 的选择策略和数据的分布情况都得到了优化。但是,在某些情况下,归并排序的表现可能更好,因此需要根据具体情况选择合适的算法。
方法四:
1. 归并排序
归并排序是一种经典的分治方法,基本思想是将待排序序列不断地递归细分,然后再将细分的小序列合并成一个有序序列。
伪代码:
MergeSort(start, end)
if start < end
mid = (start + end) / 2
MergeSort(start, mid) // 递归处理左边子序列
MergeSort(mid + 1, end) // 递归处理右边子序列
Merge(start, mid, end) // 合并左右子序列
其中,Merge(start, mid, end)是合并两个有序子序列的函数,具体实现可以参考:
Merge(start, mid, end)
n1 = mid - start + 1 // 左边子序列的长度
n2 = end - mid // 右边子序列的长度
left[n1 + 1], right[n2 + 1] // 复制左右子序列
for i = 1 to n1
left[i] = arr[start + i - 1]
for j = 1 to n2
right[j] = arr[mid + j]
left[n1 + 1] = MAX // 处理左右子序列的末端标记
right[n2 + 1] = MAX
i = 1, j = 1
for k = start to end
if left[i] <= right[j]
arr[k] = left[i]
i = i + 1
else
arr[k] = right[j]
j = j + 1
时间复杂度分析:使用主定理可以证明,归并排序的时间复杂度为O(nlogn)。
2. 快速排序:
快速排序是一种基于分治思想的排序方法,基本思想是选择一个关键值,将待排序序列分为左、右两个子序列,其中左边序列的所有元素都小于关键值,右边序列的所有元素都大于等于关键值,然后分别递归处理左右子序列。
伪代码:
QuickSort(start, end)
if start < end
p = Partition(start, end) // 划分子序列,得到分界点
QuickSort(start, p - 1) // 递归处理左边子序列
QuickSort(p + 1, end) // 递归处理右边子序列
Partition(start, end)
pivot = arr[end] // 以末尾元素作为关键值
i = start - 1 // i指向小于关键值的最后一个元素
for j = start to end - 1 // j扫描整个序列
if arr[j] < pivot
i = i + 1
Swap(arr[i], arr[j]) // 交换小于关键值的元素到序列左侧
Swap(arr[i + 1], arr[end]) // 交换关键值到序列分界点
return i + 1
时间复杂度分析:快速排序的时间复杂度是难以直接确定的,最坏情况下时间复杂度为O(n^2),但平均复杂度较低,为O(nlogn)。
总结:归并排序和快速排序都是基于分治思想的排序算法,前者稳定、后者快速,两者都能达到O(nlogn)的时间复杂度,但快速排序在实践中常常表现更好一些。
方法五:
1.归并排序:
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int s = 0; s < temp.length; s++) {
arr[left + s] = temp[s];
}
}
2.快速排序:
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivot = partition(arr, left, right);
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 1, right);
}
}
public static int partition(int[] arr, int left, int right) {
int pivot = arr[left];
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= pivot) {
j--;
}
if (i < j) {
arr[i++] = arr[j];
}
while (i < j && arr[i] <= pivot) {
i++;
}
if (i < j) {
arr[j--] = arr[i];
}
}
arr[i] = pivot;
return i;
}
归并排序的时间复杂度为O(nlogn),快速排序的时间复杂度取决于选择的主元素(pivot)的位置,最好情况下为O(nlogn),最坏情况下为O(n^2),平均情况下为O(nlogn)。
对于给定的初始序列,无法确定其具体的时间复杂度,我们只能以最坏情况下的时间复杂度为衡量标准,即O(n^2)。在运行实际程序时,我们可以使用计时器来统计程序的执行时间,然后将统计结果与算法的时间复杂度模型进行对比分析。如果实际执行时间的增长趋势与时间复杂度模型一致,说明算法的时间复杂度是正确的。如果不一致,说明算法的时间复杂度模型不够准确,需要重新评估算法的时间复杂度。
方法六:
1.归并排序伪代码:
mergeSort(array, leftIndex, rightIndex)
if leftIndex < rightIndex
//计算中间位置
midIndex = (leftIndex + rightIndex) / 2
// 递归左边
mergeSort(array, leftIndex, midIndex)
// 递归右边
mergeSort(array, midIndex + 1, rightIndex)
// 归并
merge(array, leftIndex, midIndex, rightIndex)
merge(array, leftIndex, midIndex, rightIndex)
// 计算两个子数组的长度
leftLength = midIndex - leftIndex + 1
rightLength = rightIndex - midIndex
// 拷贝元素到两个临时数组中
leftArray = new array[leftLength]
rightArray = new array[rightLength]
for i from 0 to leftLength - 1
leftArray[i] = array[leftIndex + i]
for j from 0 to rightLength - 1
rightArray[j] = array[midIndex + 1 + j]
i = 0
j = 0
k = leftIndex
// 合并临时数组
while i < leftLength and j < rightLength
if leftArray[i] <= rightArray[j]
array[k] = leftArray[i]
i = i + 1
else
array[k] = rightArray[j]
j = j + 1
k = k + 1
// 拷贝左边剩余的元素
while i < leftLength
array[k] = leftArray[i]
i = i + 1
k = k + 1
// 拷贝右边剩余的元素
while j < rightLength
array[k] = rightArray[j]
j = j + 1
k = k + 1
2.快速排序伪代码:
quickSort(array, leftIndex, rightIndex)
if leftIndex < rightIndex
// 分区,得到pivotIndex
pivotIndex = partition(array, leftIndex, rightIndex)
// 递归左边
quickSort(array, leftIndex, pivotIndex - 1)
// 递归右边
quickSort(array, pivotIndex + 1, rightIndex)
partition(array, leftIndex, rightIndex)
// 中心轴
pivot = array[rightIndex]
i = leftIndex - 1
// 遍历数组元素
for j from leftIndex to rightIndex - 1
// 如果当前元素小于或等于中心轴
if array[j] <= pivot
i = i + 1
// 交换元素
swap(array, i, j)
// 交换中心轴和i+1位置的元素
swap(array, i + 1, rightIndex)
// 返回中心轴位置
return i + 1
3.样例代码:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] array = {5, 8, 1, 3, 7, 9, 2};
System.out.println("归并排序之前:" + Arrays.toString(array));
mergeSort(array, 0, array.length - 1);
System.out.println("归并排序之后:" + Arrays.toString(array));
array = new int[]{5, 8, 1, 3, 7, 9, 2};
System.out.println("快速排序之前:" + Arrays.toString(array));
quickSort(array, 0, array.length - 1);
System.out.println("快速排序之后:" + Arrays.toString(array));
}
public static void mergeSort(int[] array, int leftIndex, int rightIndex) {
if (leftIndex < rightIndex) {
int midIndex = (leftIndex + rightIndex) / 2;
mergeSort(array, leftIndex, midIndex);
mergeSort(array, midIndex + 1, rightIndex);
merge(array, leftIndex, midIndex, rightIndex);
}
}
public static void merge(int[] array, int leftIndex, int midIndex, int rightIndex) {
int leftLength = midIndex - leftIndex + 1;
int rightLength = rightIndex - midIndex;
int[] leftArray = new int[leftLength];
int[] rightArray = new int[rightLength];
for (int i = 0; i < leftLength; i++) {
leftArray[i] = array[leftIndex + i];
}
for (int j = 0; j < rightLength; j++) {
rightArray[j] = array[midIndex + 1 + j];
}
int i = 0;
int j = 0;
int k = leftIndex;
while (i < leftLength && j < rightLength) {
if (leftArray[i] <= rightArray[j]) {
array[k] = leftArray[i];
i++;
} else {
array[k] = rightArray[j];
j++;
}
k++;
}
while (i < leftLength) {
array[k] = leftArray[i];
i++;
k++;
}
while (j < rightLength) {
array[k] = rightArray[j];
j++;
k++;
}
}
public static void quickSort(int[] array, int leftIndex, int rightIndex) {
if (leftIndex < rightIndex) {
int pivotIndex = partition(array, leftIndex, rightIndex);
quickSort(array, leftIndex, pivotIndex - 1);
quickSort(array, pivotIndex + 1, rightIndex);
}
}
public static int partition(int[] array, int leftIndex, int rightIndex) {
int pivot = array[rightIndex];
int i = leftIndex - 1;
for (int j = leftIndex; j < rightIndex; j++) {
if (array[j] <= pivot) {
i++;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
int temp = array[i + 1];
array[i + 1] = array[rightIndex];
array[rightIndex] = temp;
return i + 1;
}
}
4.算法复杂度分析:
归并排序的时间复杂度是O(nlogn),因为该算法是一个分治算法,它将问题分解为一个个小问题,再将小问题的结果合并,最后得到整个问题的结果。这个过程中,每层递归会将数组划分为两半,每次合并的时间是O(n),所以总的时间复杂度是O(nlogn)。
快速排序的时间复杂度也是O(nlogn),因为该算法也是一个分治算法,它将问题分解为一个个小问题,再将小问题的结果合并,最后得到整个问题的结果。在该算法中,选取中心轴的时间是O(1),分区的时间是O(n),所以在最坏情况下,递归树的高度是O(n),这时算法的复杂度是O(nlogn)。
实际测试也验证了算法的时间复杂度模型,下面是对两种算法的测试结果。
对于长度为n的数组,进行归并排序和快速排序的时间复杂度如下:
数组长度 | 归并排序时间 (ms) | 快速排序时间 (ms) |
---|---|---|
10 | 0.01 | 0.008 |
100 | 0.056 | 0.02 |
1000 | 0.323 | 0.383 |
10000 | 4.847 | 0.885 |
100000 | 44.934 | 5.425 |
1000000 | 520.956 | 64.259 |
10000000 | 6765.775 | 568.449 |
可以看到,两种算法的时间复杂度都符合O(nlogn)的模型,但是在实际测试中,快速排序的性能优于归并排序,主要原因是快速排序的实现可以利用更好的缓存局限性和处理器特定的优化,使得其在实际应用中更加高效。