折半插入排序,加一个条件判断会得到优化吗?

本文探讨了如何通过在折半插入排序中加入条件判断,减少不必要的元素移动,针对不同元素趋势分析其对时间复杂度的影响。实验证明,在升序排列中少量节省比较次数,但在整体下降趋势中效果不明显。

一、直接插入排序

在直接插入排序的基础上,由于在查找插入位置时,所查找的序列是有序序列,故可以利用折半二分查找算法来优化查找的效率。总体思想就是先用折半查找找到应该插入的位置,再移动元素

先来看看直接插入排序算法代码:

void InSertSort(int a[],int length)
{
	int i,j;
	for(i=2;i<length;i++)
	{
		a[0]=a[i];//将a[0]作为哨兵,存放待插入的元素
		for(j=i-1;a[j]>a[0];j--)
		{
			a[j+1]=a[j];//记录后移
		}
		a[j+1]=a[0];//插入到正确位置
	}
}

当查找第i个元素的插入位置之前,首先在for循环中的判断条件是第i个元素要比第i-1个元素小,这样才会执行循环内部代码。第i个元素前面的元素默认已经递增排好序的,如果第i个元素大于它前面那个元素,这时依然是递增,不需要移动第i个元素,可以直接跳过本次循环,判断下个元素。

二、折半插入排序

再来看看折半插入排序算法代码:

//对数组进行折半插入排序(带哨兵)
void InsertSort_Y(int arr[], int len) {
    int i, j, low, high, mid;
    for (i = 2; i < len; ++i) {             //依次将数组中arr[2]~arr[n]插入前面已经排序序列
        arr[0] = arr[i];                    //将arr[i]复制到哨兵点arr[0]
        low = 1;                            //设置折半查找范围
        high = i - 1;
        while (low <= high) {               //折半查找(默认递增有序)
            mid = (low + high) / 2;         //取中间点
            if (arr[mid] > arr[0])
                high = mid - 1;             //查找右半子表
            else
                low = mid + 1;              //查左半子表
        }
        for (j = i - 1; j >= high + 1; --j)
            arr[j + 1] = arr[j];            //依次后移元素,空出插入位
        arr[high + 1] = arr[0];             //插入
    }
}

上面代码中在查找第i个元素的插入位置之前并没有像直接插入排序那样先判断一下第i个元素是否需要移动,即使第i个元素比它前面所有元素都大(这样不需要移动),依然需要对前面的元素执行折半查找,这就是令人们困惑的地方,为什么不加上一句if判断语句,如果arr[i] > arr[i - 1] ,是不是就可以省略本次对它前面元素的折半查找呢?

三、折半查找排序加上猜想

加上猜想的那句代码:

void InsertSort_C(int arr[], int len) {
    int i, j, low, high, mid;
    for (i = 2; i < len; ++i) {                 //依次将数组中arr[2]~arr[n]插入前面已经排序序列
        if(arr[i] < arr[i-1]){
            arr[0] = arr[i];                    //将arr[i]复制到哨兵点arr[0]
            low = 1;                            //设置折半查找范围
            high = i - 1;
            while (low <= high) {               //折半查找(默认递增有序)
                mid = (low + high) / 2;         //取中间点
                if (arr[mid] > arr[0])
                    high = mid - 1;             //查找右半子表
                else
                    low = mid + 1;              //查左半子表
            }
            for (j = i - 1; j >= high + 1; --j)
                arr[j + 1] = arr[j];            //依次后移元素,空出插入位
            arr[high + 1] = arr[0];             //插入
        }
    }
}

四、前后对比分析:

原来的折半插入排序相关性能指标:加入判断后的折半插入排序相关性能指标:
时间复杂度时间复杂度
最好情况: O(nlog⁡2n\log_2^nlog2n )最好情况: O(n )
最坏情况: O(n2n^2n2)最坏情况: O(n2n^2n2)
平均情况: O(n2n^2n2)平均情况: O(n2n^2n2)
空间复杂度: O(1)空间复杂度: O(1)
稳定性 :稳定稳定性 :稳定

相比之下也就最好情况下的结果好一点,平均情况没有实质性的变化,因为只有当第i个元素比它前面所有元素都大时,才可以省略一次折半查找,其他情况下还是需要进行折半查找,而且多了一次比较。
总结一下就是,假如需要排序为升序时,当所有元素总体上呈上升趋势时,可以加上第i个元素是否需要移动的判断语句,但当整体元素呈现下降趋势时,加上这条判断可能只会增加一点比较的次数。但是加不加总体上看好像几乎没什么影响。

五、后来的话

经过后来的n次随机数测试,更多情况下,不加那句判断语句的比较次数要小于加上判断语句,也就是说,所谓的最好情况变好了点在实际应用中可能只是假象,所以一般情况还是不要加了。

下面是测试的代码,感兴趣的可以运行研究一下:

//插入排序————折半插入排序(稳定,空间复杂度为O(1),时间复杂度为O(n^2))
#include <stdio.h>
#include <stdlib.h>


//对数组进行折半插入排序(带哨兵)
int InsertSort_Y(int arr[], int len)
{
    int i, j, low, high, mid;
    int compare_move_times = 0;
    for (i = 2; i < len; ++i)               //依次将数组中arr[2]~arr[n]插入前面已经排序序列
    {
        arr[0] = arr[i];                    //将arr[i]复制到哨兵点arr[0]
        low = 1;                            //设置折半查找范围
        high = i - 1;
        while (low <= high)                 //折半查找(默认递增有序)
        {
            mid = (low + high) / 2;         //取中间点
            if (arr[mid] > arr[0])
                high = mid - 1;             //查找右半子表
            else
                low = mid + 1;              //查左半子表
            compare_move_times++;
        }
        for (j = i - 1; j >= high + 1; --j)
        {
            arr[j + 1] = arr[j];            //依次后移元素,空出插入位
            compare_move_times++;
        }
        arr[high + 1] = arr[0];             //插入
    }
    return compare_move_times;
}

int InsertSort_C(int arr[], int len)
{
    int i, j, low, high, mid;
    int compare_move_times = 0;
    for (i = 2; i < len; ++i)               //依次将数组中arr[2]~arr[n]插入前面已经排序序列
    {
        if(arr[i] < arr[i-1])
        {
            compare_move_times++;
            arr[0] = arr[i];                    //将arr[i]复制到哨兵点arr[0]
            low = 1;                            //设置折半查找范围
            high = i - 1;
            while (low <= high)                 //折半查找(默认递增有序)
            {
                mid = (low + high) / 2;         //取中间点
                if (arr[mid] > arr[0])
                    high = mid - 1;             //查找右半子表
                else
                    low = mid + 1;              //查左半子表
                compare_move_times++;
            }
            for (j = i - 1; j >= high + 1; --j)
            {
                arr[j + 1] = arr[j];            //依次后移元素,空出插入位
                compare_move_times++;
            }
            arr[high + 1] = arr[0];             //插入
        }
        else
            compare_move_times++;

    }
    return compare_move_times;
}

void Test_C(int arr[],int len)
{
    printf("\n--------------------带上判断条件--------------------\n");
    printf("\n  数组初始数据为:");
    for (int i = 1; i < len; ++i)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    int times = InsertSort_C(arr, len);
    //将已排序的结果逐个输出
    printf("带哨兵的数组进行折半插入排序\n");
    printf("数组排序结果为:");
    for (int i = 1; i < len; ++i)
    {
        printf("%d ", arr[i]);
    }
    printf("\n比较的次数times为:%d \n",times);
}

void Test_Y(int arr_y[],int len)
{
    printf("\n--------------------不带判断条件--------------------\n");
    printf("\n数组初始数据为:");
    for (int i = 1; i < len; ++i)
    {
        printf("%d ", arr_y[i]);
    }
    printf("\n");
    int times = InsertSort_Y(arr_y, len);
    //将已排序的结果逐个输出
    printf("带哨兵的数组进行折半插入排序\n");
    printf("数组排序结果为:");
    for (int i = 1; i < len; ++i)
    {
        printf("%d ", arr_y[i]);
    }
    printf("\n比较的次数times为:%d \n",times);
}

int main()
{

    //带哨兵的数组进行快排
    int arr[] = {0, 49, 38, 65, 97, 76, 13, 27, 49, 33, 78, 91, 19, 10, 28, 3, 90, 77, 1, 11, 21, 15, 99, 66, 55, 88, 70};
    //arr1为[1,99]的正序排序,arr2为[1,99]的倒序排序
    int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99};
    int arr2[] = {0, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    int len = sizeof(arr) / sizeof(int);

    //Test_Y(arr1,100);//比较或移动次数为 566 次
    //Test_C(arr1,100);//比较或移动次数为 98 次
    //Test_Y(arr2,100);//比较或移动次数为 5325 次
    //Test_C(arr2,100);//比较或移动次数为 5423 次
    //Test_Y(arr,len);  //比较或移动次数为 252 次
    //Test_C(arr,len);//比较或移动次数为 268 次


    int arr3[51],brr3[51];
    arr3[0] = 0;
    brr3[0] = 0;
    srand(time(0));
    for(int i = 1; i < 50; i++)
    {
        arr3[i] = rand()%100;      //产生50个0-99的随机数
        brr3[i] = arr3[i];
    }
    Test_Y(arr3,51);
    Test_C(brr3,51);
    
    return 0;
}
### 各种内部排序算法的时间复杂度及关键字比较移动次数的实验性对比分析 以下是对直接插入排序折半插入排序、二路插入排序、希尔排序、起泡排序、快速排序、简单选择排序、堆排序、归并排序和基数排序的关键字比较次数和移动次数的实验性对比分析。代码实现如下: ```python import random import time def insertion_sort(arr): comparisons = 0 moves = 0 for i in range(1, len(arr)): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: comparisons += 1 arr[j + 1] = arr[j] moves += 1 j -= 1 arr[j + 1] = key if j >= 0: # 如果没有发生交换,比较次数仍需一 comparisons += 1 return comparisons, moves def binary_insertion_sort(arr): comparisons = 0 moves = 0 for i in range(1, len(arr)): key = arr[i] left, right = 0, i - 1 while left <= right: mid = (left + right) // 2 comparisons += 1 if key < arr[mid]: right = mid - 1 else: left = mid + 1 for j in range(i, left, -1): arr[j] = arr[j - 1] moves += 1 arr[left] = key return comparisons, moves def shell_sort(arr): comparisons = 0 moves = 0 n = len(arr) gap = n // 2 while gap > 0: for i in range(gap, n): temp = arr[i] j = i while j >= gap and arr[j - gap] > temp: comparisons += 1 arr[j] = arr[j - gap] moves += 1 j -= gap if j >= gap: comparisons += 1 arr[j] = temp gap //= 2 return comparisons, moves def bubble_sort(arr): comparisons = 0 moves = 0 n = len(arr) for i in range(n): for j in range(0, n - i - 1): comparisons += 1 if arr[j] > arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] moves += 1 return comparisons, moves def quick_sort(arr): comparisons = [0] moves = [0] def partition(low, high): pivot = arr[high] i = low - 1 for j in range(low, high): comparisons[0] += 1 if arr[j] <= pivot: i += 1 arr[i], arr[j] = arr[j], arr[i] moves[0] += 1 arr[i + 1], arr[high] = arr[high], arr[i + 1] moves[0] += 1 return i + 1 def quick_sort_recursive(low, high): if low < high: pi = partition(low, high) quick_sort_recursive(low, pi - 1) quick_sort_recursive(pi + 1, high) quick_sort_recursive(0, len(arr) - 1) return comparisons[0], moves[0] def selection_sort(arr): comparisons = 0 moves = 0 for i in range(len(arr)): min_idx = i for j in range(i + 1, len(arr)): comparisons += 1 if arr[min_idx] > arr[j]: min_idx = j if min_idx != i: arr[i], arr[min_idx] = arr[min_idx], arr[i] moves += 1 return comparisons, moves def heap_sort(arr): comparisons = [0] moves = [0] def heapify(n, i): largest = i left = 2 * i + 1 right = 2 * i + 2 if left < n and arr[largest] < arr[left]: comparisons[0] += 1 largest = left if right < n and arr[largest] < arr[right]: comparisons[0] += 1 largest = right if largest != i: arr[i], arr[largest] = arr[largest], arr[i] moves[0] += 1 heapify(n, largest) n = len(arr) for i in range(n // 2 - 1, -1, -1): heapify(n, i) for i in range(n - 1, 0, -1): arr[i], arr[0] = arr[0], arr[i] moves[0] += 1 heapify(i, 0) return comparisons[0], moves[0] def merge_sort(arr): comparisons = [0] moves = [0] def merge(left, right): result = [] i = j = 0 while i < len(left) and j < len(right): comparisons[0] += 1 if left[i] < right[j]: result.append(left[i]) moves[0] += 1 i += 1 else: result.append(right[j]) moves[0] += 1 j += 1 result.extend(left[i:]) result.extend(right[j:]) return result if len(arr) <= 1: return arr mid = len(arr) // 2 left = merge_sort(arr[:mid]) right = merge_sort(arr[mid:]) return merge(left, right) def radix_sort(arr): comparisons = 0 moves = 0 max_num = max(arr) exp = 1 while max_num // exp > 0: buckets = [[] for _ in range(10)] for number in arr: index = (number // exp) % 10 buckets[index].append(number) moves += 1 arr.clear() for bucket in buckets: arr.extend(bucket) moves += len(bucket) exp *= 10 return comparisons, moves # 测试代码 algorithms = { "Insertion Sort": insertion_sort, "Binary Insertion Sort": binary_insertion_sort, "Shell Sort": shell_sort, "Bubble Sort": bubble_sort, "Quick Sort": quick_sort, "Selection Sort": selection_sort, "Heap Sort": heap_sort, "Merge Sort": merge_sort, "Radix Sort": radix_sort } data = [random.randint(1, 1000) for _ in range(100)] results = {} for name, func in algorithms.items(): arr_copy = data.copy() start_time = time.time() comparisons, moves = func(arr_copy) elapsed_time = time.time() - start_time results[name] = {"Comparisons": comparisons, "Moves": moves, "Time": elapsed_time} for name, result in results.items(): print(f"{name}: Comparisons={result['Comparisons']}, Moves={result['Moves']}, Time={result['Time']:.6f} seconds") ``` 上述代码实现了十种排序算法,并统计了关键字比较次数和移动次数。时间复杂度总结如下[^4]: - 直接插入排序:O(n²) - 折半插入排序:O(n²) - 二路插入排序:O(n²) - 希尔排序:O(n^1.3) ~ O(n²) - 起泡排序:O(n²) - 快速排序:O(n log n) - 简单选择排序:O(n²) - 堆排序:O(n log n) - 归并排序:O(n log n) - 基数排序:O(d(n+r))
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值