linux: 堆排序和快速排序的整理

快排采用分治法(Divide and Conquer)把一个list分为两个sub-lists。

算法步骤

1. 从数列中跳出一个元素,作为基准(pivot)。

2. 重新排序数列,所有比基准值小的元素(elements < pivot)放在基准值的前面,而所有比基准值大的元素(elements > pivot)放在基准值后面,与基准值相等的数可以放在任意一边。此操作即为分区(partition)操作。

3. 递归地把小于基准值元素的子数列和大于基准值元素的子数列进行排序。递归在数列长度为0或者1时退出,此时该子数列已经永久排序完成。

1)如数列5 8 7 …,若此时5归位,那么其 left sub-list 的长度为0,退出递归。

2)如数列1 2 8 4…,若此时2归位,那么其 left sub-list的长度为1,退出递归。

注意:该算法一定是可以退出的,因为在每次的迭代中,它

使用快慢指针。仍以数列第一个元素为基准元素。初始化时,慢指针指向基准元素,也就是数列第一个元素,快指针指向数列第二个元素。快指针用于遍历数列。当程序运行时,慢指针将指向比基准元素小的元素的最后一个位置。当快指针指向的元素小于满指针指向的元素时,才进行交换,其实就是将小于基准元素的元素的位置往前移(思想类似于去空格,参看此博文)。

partition代码如下:

int partion(int *arr, int len)
{
    int last = 0;
    int fast = 1;
    int key = arr[0];
    for(;fast < len; fast++)
    {
        if(arr[fast] < key)
        {
            swap(&arr[last+1], &arr[fast]);
            last++;
        }
    }
    swap(&arr[0], &arr[last]); //归位
    return last;
}

至少会把一个元素放在它最后应该在的位置。

引言

首先需要明确,如何根据父亲结点的位置得知孩子结点的位置,以及如何根据孩子结点的位置得知父亲结点的位置。

假设数列索引从0开始,如果父亲结点的索引为i,那么左孩子索引为2i+1,右孩子索引为2i+2;如果孩子结点的索引为j,那么父亲结点的索引为(j-1)/2。

堆排序的核心在于函数 void adjustdown(int *arr, int i, int end) ,其中第i+1个元素到最后一个元素均已满足堆结构,每次adjustdown可以使当前位置i的元素也满足堆结构。如果是大堆,则经过adjustdown后当前位置的元素最大;如果是小堆,则经过adjustdown后当前位置的元素最小。

以下代码,将数列从小到大排序。采用大堆,并且使用了非递归的方法。函数adjustdown中的五个if语句实际上有一部分是可以合并的,但是为了逻辑清晰,在本代码中,完整保留下来了。注释我写的很用心,相信读者可以看懂。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static void show(int *arr, int len)
{
    int index;
    for(index = 0; index < len; index++)
    {
        printf("%d ",arr[index]);
    }
    printf("\n");
}

static void swap(int *left, int *right)
{
    int tmp = *left;
    *left = *right;
    *right = tmp;
}

void adjustdown(int *arr, int i, int end)
{
    int key = arr[i];
    int p = i;
    int left = 2 * p + 1;
    /* 越界就是没孩子 */    /* 只要能进循环,一定有左孩子 */
    while( left <= end )
    {
        /* 有右孩子的情况下,大于等于左右孩子不用换 */
        if( (key >= arr[left]) && (left+1 <= end && key >= arr[left+1]))
        {
            break;
        }else if( key >= arr[left] && left + 1 > end) /* 没有右孩子,只有左孩子,且大于等于左孩子不用换*/
        {
            break;
        }else if(left + 1 <= end && arr[left+1] >= arr[left] && key < arr[left+1]) /* 与右孩子换。要保证有右孩子,且右孩子大于等于左孩子,父亲小于右孩子 */
        {
            swap(arr+p, arr+left+1);
            p = left + 1;         //父亲与谁换,就到谁的位置了
             left = 2 * p + 1;      //父亲新的左孩子的位置
        }else if(left + 1 <= end && arr[left] > arr[left + 1] && key < arr[left])/* 与左孩子换。有右孩子的情况下,右孩子小于左孩子,父亲小于左孩子 */
        {
            swap(arr + p, arr + left);
            p = left;
            left = 2 * p + 1;
        }else if(left + 1 > end && arr[left] > key) /* 与左孩子换。没右孩子的情况下,只需父亲小于左孩子 */
        {
            swap(arr + p, arr + left);
            p = left;
            left = 2 * p + 1;
        }
    }
}

void heap_sort(int *arr, int len)
{
    int p;   // 最后一个父亲
    int end; // 最后一个有效下标
    /* 建一个大顶堆,从最后一个父亲开始调 */
    for(p = (len -1 -1) /2 ; p >= 0; p--)
    {
        adjustdown(arr, p ,len - 1);
    }
    /* 根结点的值最大,与末尾交换,并继续建立堆结构,再交换... */
    for(end = len - 1; end >= 1; end--)
    {
        swap(arr, arr + end );   // end已经是最大值
        adjustdown(arr,0,end-1);  // 从arr+1 到 end-1位置都是满足堆结构的
    }
}

int main(int argc, char *argv[])
{
    int index;
    int arr[10];
    memset(arr,0,10);
    srand(time(NULL));
    for(index = 0; index < 10; index++)
    {
        arr[index] = rand()%20+1;
    }
    show(arr,10);

    heap_sort(arr,10);
    show(arr,10);

    system("pause");
    return 0;
}

adjustdown这个函数是可以整理成4个判断的,更加简洁明了,但是我们今天先不整理,这个版本在思路上更加直接, 等我们有时间再来整理。

时间: 2024-08-23 15:54:05

linux: 堆排序和快速排序的整理的相关文章

堆排序与快速排序

前言 前面差不多学习了插入排序.选择排序.冒泡排序.归并排序.这些排序除了归并排序在时间上消耗为:θ(nlgn)外,其余排序时间消耗都为:θ(n2). 接下来要讲的就是两种比较优雅的比较排序算法:堆排序和快速排序. 堆排序最坏情况下可以达到上界:ο(nlgn).快速排序平均情况下可以达到:θ(nlgn). 堆排序 堆排序的关键在于完全二叉树.堆排序开始要构建一个完全二叉树,且该完全二叉树必须满足某一个结点大于左子节点的值和右子结点的值.我们假设根结点中的值为数组中下标为0的元素,根结点的左子结点

小知识-为什么Linux不需要磁盘碎片整理

转载至:http://beikeit.com/post-495.html 简单译文: 这段linux官方资料主要介绍了外部碎片(external fragmentation).内部碎片(internal fragmentation)的概念及相关情况,说明了linux文件系统在磁盘还有5%空闲空间的情况下是不需要碎片整理的.(Linux native file systems do not need defragmentation under normal use and this include

堆排序--采用快速排序(利用大堆实现升序,小堆实现降序)

对堆进行排序,利用大堆实现升序,小堆实现降序.例如升序的实现,将较大数据存放在最后面,依次往前存放数据.具体为交换第一个元素和最后一个元素,再将不包含最后一个元素的堆进行下调,使堆保持大堆,将最大数据存放在堆中第一个位置,循环执行上述步骤,直到需要下调的数据个数为0. void AdjustDown(int *a, size_t root, size_t size)//下调--k为数组下标,size为数组元素个数 {//大堆 size_t parent = root; size_t child 

Linux的经典shell命令整理

Linux的经典shell命令整理 1.删除0字节文件find -type f -size 0 -exec rm -rf {} \; 2.查看进程按内存从大到小排列ps -e -o “%C : %p : %z : %a”|sort -k5 -nr 3.按cpu利用率从大到小排列ps -e -o “%C : %p : %z : %a”|sort -nr 4.打印说cache里的URLgrep -r -a jpg /data/cache/* | strings | grep “http:” | aw

Linux初学者进阶学习资源整理

Linux初学者进阶学习资源整理 实验楼分享的Linux学习路径,用图文并茂的形式清晰直观的告诉了Linux初学者该如何从一个新手小白进阶成为Linux高手. 不过这条Linux学习路径到底只是一个学习计划,没能有详细的教程提供给大家学习.回想之前写的(干货)Linux学习资源推荐,也只是比较全面的列举了可以学习Linux的地方. 于是乎便有了这篇Linux初学者进阶学习资源整理,将会按照学习路径的知识点学习为大家提供详细的教程(不限于视频.书籍.网络教程.技术博客等资源). Linux初级入门

各种常见的排序,冒泡排序,选择排序,插入排序,希尔排序,堆排序,快速排序,基数排序,桶排序

各种常见的排序 要开始找工作了,把以前学的各种小知识复习一遍,以下是各种常见的排序的简单实现(冒泡排序,选择排序,插入排序,希尔排序,堆排序,快速排序,基数排序,桶排序),至于原理就不写出来了,代码比较简单,看一下就懂,再不行可以随意找本书或百度! #include <iostream> using namespace std; // 冒泡 void BubbleSort(int data[], int length) { if(data == NULL || length <= 0)

五种排序算法整理 二(堆排序,快速排序、插入排序、选择排序、冒泡排序)

一.快速排序算法步骤: 1. 在数组中选一个基准数(通常为数组第一个): 2. 将数组中小于基准数的数据移到基准数左边,大于基准数的移到右边: 3. 对于基准数左.右两边的数组,不断重复以上两个过程,直到每个子集只有一个元素,即为全部有序. 实例演示 1.将第一个元素49设置为基准,low=0,high=7. 因为49不小于49,所以不换位置,high--. 2.27小于49,所以27和49互换位置(图上没有显示出来,但这并不影响)low++ 3.我们再从基准49的位置和low对应的38比较,发

Linux动态库相关知识整理

动态库和静态库在C/C++开发中很常见,相比静态库直接被编译到可执行程序, 动态库运行时加载使得可执行程序的体积更小,更新动态库可以不用重新编译可执 行程序等诸多好处.作者是一个Linux后台开发,这些知识经常用到,所以 整理了一下这方面的知识.静态库相对简单,本文只关心Linux平台下的动态库. 创建动态库 这里我把一个短小却很有用的哈希函数编译成动态库做为示例,ELFhash用于对字符串做哈希,返回一个无符号整数. //elfhash.h #include <stdio.h> unsign

快速排序思路整理

引言: 快速排序和归并排序是面试当中常常被问到的两种排序算法,在研究过数据结构所有的排序算法后,个人认为最复杂的当属快速排序.从代码量上来看,快速排序并不多,我之所以认为快排难以掌握是因为快排是一种递归算法,同时终止条件较多.如果你刚刚把快排的思路整理过一遍可能觉得不难,然而一个月之后呢? 面试要求的是临场发挥,如果不是烂熟于心,基本就卡壳了.在面试官眼里,你和那些完全不懂快速排序算法的菜逼是一样的,也许实际上你可能私底下已经理解很多遍了,然而并没卵.所以当下问题就是,如何将快排烂熟于心?我觉得