剑指Offer-快速排序

剑指Offer上的快速排序的Partition函数与我在数据结构书上学到的不一样,因此就想要探索下这两种不同的处理方式。

基本思想

快速排序的基本思想是基于分治法。在待排序表L[1...n]中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两部分L[1...k-1]和L[k+1...n],使得L[1...k-1]中所有元素小于pivot,L[k+1...n]中所有元素大于或等于pivot,则pivot放在了其最终位置L(k)上。而后递归地对两个子表重复上述过程,直至每部分内只有一个元素或空为止,即所有元素放在了其最终位置上。

剑指Offer上的Partition实现

// Partition为分割函数, length表示data数组的长度
int Partition(int data[], int length, int low, int high) {
    if (data == nullptr || length <= 0 || low < 0 || high >= length) {
        return 1;
    }
    
    // 在[low, high]区间中随机取一个基准值,并将其换到区间最末尾
    int index = RandomInRange(low, high);
    Swap(&data[index], &data[high]);
    
    // small在low的前面一位
    int small = low - 1;
       
    for (index = low; index < high; ++index) {
        if (data[index] < data[high]) {
            // ++small后表示大于基准值的第一个数的下标
            // 如果不存在大于基准值的数,则表示当前比较的数字下标
            ++small;

            // 交换后的small表示小于基准值的最后一个数字的下标
            if (small != index) {
                Swap(&data[index], &data[small]);
            }
        }
    }
    
    // ++small后表示大于基准值的第一个数的下标
    ++small;
    Swap(&data[small], &data[high]);
    
    return small;
    
}

快速排序其它函数实现如下:


// 生成一个随机数,范围在[low,high),是一个前闭后开的区间
int RandomInRange(int low, int high) {
    return rand() % (high - low) + low;
}

// 交换两个整数
void Swap(int* left, int* right) {
    int temp  = *left;
    *left = *right;
    *right = temp;
}

// 快速排序函数
void QuickSort(int data[], int length, int low, int high) {
    if (low == high) {
        return;
    }
    
    int index = Partition(data, length, low, high);
    
    if (index > low) {
        QuickSort(data, length, low, index - 1);
    }
    
    if (index < high) {
        QuickSort(data, length, index + 1, high);
    }
}

// 数据打印
void PrintData(int data[], int length) {
    for (int i = 0; i < length; ++i) {
        std::cout << data[i] << " ";
    }
    
    std::cout << std::endl;
}

int main() {
    // 以系统时间作为随机函数种子
    srand((unsigned)time(nullptr));

    int const length = 10;
    
    int data[length];

    // 生成随机数
    for (int i = 0; i < length; ++i) {
        data[i] = RandomInRange(1, length*length);
    }
    
    // 打印生成的随机数
    PrintData(data, length);
    
    // 调用快排函数进行排序
    QuickSort(data, length, 0, length - 1);
  
    // 打印快速排序后的数
    PrintData(data, length);
    
    return 0;
    
}

初始顺序为{49, 38, 65, 97, 76, 13, 27},基准值为49的数据的第一趟快速排序的实现如下所示:

步骤 small index
0 49 38 65 97 76 13 27
1 27 38 65 97 76 13 49
2 0 0 27 38 65 97 76 13 49
3 1 1 27 38 65 97 76 13 49
4 1 2 27 38 65 97 76 13 49
5 1 3 27 38 65 97 76 13 49
6 1 4 27 38 65 97 76 13 49
7 2 5 27 38 13 97 76 65 49
8 3 6 27 38 13 49 76 65 97

第一趟排序完之后基准值49之前的{27, 38, 13}都比49小,基准值49之后的{76, 65, 97}都比49大。

一般教材上的Partition实现

// Partition为分割函数
int Partition(int data[], int low, int high) {

    // 将当前表中第一个元素设为基准值,对表进行划分
    int pivot = data[low]; 

    // 循环跳出条件
    while (low < high) {
        while (low < high && data[high] >= pivot) {
            --high;
        }

        // 将比基准值小的元素移动到左端
        data[low] = data[high];

        while (low < high && data[low] <= pivot) {
            ++low;
        }

        // 将比基准值大的元素移动到右端
        data[high] = data[low];
    }

    // 基准元素存放到最终位置
    data[low] = pivot;

    // 返回存放基准值的最终位置
    return low;
}

快速排序其它函数实现如下:

void QuickSort(int data[], int low, int high) {
    // 递归跳出条件
    if (low < high) {
        // 将data[low...high]一分为二,pivotpos是基准值
        int pivotpos = Partition(data, low, high);

        // 对左子表进行递归排序
        QuickSort(data, low, pivotpos - 1);

        // 对右子表进行递归排序
        QuickSort(data, pivotpos + 1, high);
    }
}

初始顺序为{49, 38, 65, 97, 76, 13, 27},基准值为49的数据的第一趟快速排序的实现如下所示:

步骤 low high
0 0 6 49 38 65 97 76 13 27
1 2 6 27 38 65 97 76 13 27
2 2 6 27 38 65 97 76 13 65
3 2 5 27 38 13 97 76 13 65
4 3 5 27 38 13 97 76 97 65
5 3 3 27 38 13 49 76 97 65

第一趟排序完之后基准值49之前的{27, 38, 13}都比49小,基准值49之后的{76, 97, 65}都比49大。

总结

可以看出,上述这两种思想导致的运行过程是不同的,前者是用small指代小于标准元素的最右位置,在遍历过程中进行交换操作来保证small左边都是小于标准元素的元素,而后者是用low,high分别表示小于和大于基准值的集合边界位置,所以后者更浅显易懂。


个人主页:

www.codeapes.cn

posted @ 2019-12-23 23:59  Codeapes  阅读(726)  评论(0编辑  收藏  举报