【算法】希尔、快速、堆、基数、归并排序算法

基本思想,实现框架,必要时画流程图

希尔排序
定义一个间隔,初始一般为数组的长度一半,将整个数组的元素按间隔归为多个组,使组内元素有序;再逐渐缩小间隔,一般在原来基础上再除以一半,重复上述过程,直到间隔缩为1,即数组本身,进行一次插入排序。而此时由于各个元素已经基本有序,所以对整个数组的排序只需移动很少的元素。

for(int gap = n/2; gap >=1; gap /= 2){
        for(int i = 0; i < gap; i++){
            for(int j = i+gap; j < n; j+=gap){
            	int temp = v[j];//插入排序,记录待插入结点
                int k = j - gap;//同一组的前一个元素,需要向前遍历到插入位置
                if(temp > v[k]){//降序,大数在前
                    while(k >= 0 && temp > v[k]){//循环找到插入位置,k不能越界
                        v[k + gap] = v[k];//将元素后移
                        k -= gap;
                    }
                    v[k + gap] = temp;
                }
            }
        }
    }

要点:
i用来选取不同组,j用来选取组内的下一个元素,j 每次增加gap,在i确定的组中遍历组内所有元素
gap循环到1结束

快速排序
采用分治法,整个数组排序的实现建立在递归子区间均已排序构成的合集。
每轮递归确定一个基准元素的位置,再以基准元素为中心将数组分为基准元素左右两个区间继续递归。
设置一个基准元素pivot(一般选首个元素或末尾元素),和两个分别指向区间的首部指针low和尾部指针high,!当选取首个元素为pivot时,先移动右指针high,右指针向左移动直到遇到比基准元素pivot小的元素然后停下,再向右移动左指针low直到遇到第一个比pivot大的元素上,交换左右指针对应的元素。重复直到左指针等于右指针,将pivot与low对应的元素交换,此时在整个数组中pivot基准元素就确定好了自己的最终位置。以基准元素二分区间继续递归

int oneSort(vector<Book> &v, int low, int high){
    int pivot = low;//pivot等于区间最左点low
    while(low < high){
            while(low < high && v[high].monthTicket >= v[pivot].monthTicket)//pivot为首元素,则high指针先移动
                high--;
            while (low < high && v[low].monthTicket <= v[pivot].monthTicket)//枢纽选的第一个数,low从0开始,包含=
                low++;
            if(low < high)
                swap(v[high], v[low]);
        }
    swap(v[low], v[pivot]);
    return low;//low是枢纽确定后的位置
}

void quickSort(vector<Book> &v){
    stack<pair<int, int>> s;
    s.push({0, v.size()-1});
    while(!s.empty()){
        弹出左右指针
        int mid = oneSort(v, low, high);//oneSort返回pivot的下标
        if(mid-1 > low)
            左右指针入栈
        if(mid+1 < high)
            左右指针入栈
    }
}

要点:
由于数据量大,不采用递归实现,而使用栈数据结构模拟递归
左右指针那个先移动与pivot选取有关,pivot为首元素,则右指针先移动
左右指针相等时,即为pivot元素的最终位置
非递归方法将区间的左右端点,即左右指针作为pair压入栈中,注意左右指针不能越界

堆排序
将数组构建出完全二叉树,从最后一个内结点向上调整,若为大根堆,则从最后一个内结点往上,每次将此根节点与左右子节点中的较大者作为根节点,直到最后完全二叉树的根节点就确定为最大数,记录到数组中。再将数组中最后一个元素覆盖到根节点位置,再次从最后一个内结点开始向上调整堆,直到堆中所有元素均已被记录

int heapSort(vector<Book> v){
    vector<int> ret;
    for(int n =v.size(); n > 0; n--){
        for(int i = (n-1-1)/2; i>=0; i--){
            找到左右子节点的最大者
            if(最大者大于根节点){
                交换最大者与根节点
            }
        }
    	ret.push_back(v[0]);//记录最大元素到返回数组
    	v[0] = v[n-1];//用尾元素覆盖根节点
    }
    return ret;
}

要点:
n-1为尾元素,(index - 1)/2为index的父节点,父节点*2为左孩子,*2+1为右孩子
从最后一个内结点(尾结点的父节点开始向上调整)

基数排序
待比较数字的拥有不同的位权,在相同的位权上可以进行排序,再逐渐升高位权,例如10, 22, 7,3, 54,按照个位上排序后为10, 22, 3, 54, 7,在此排序结果上再按照十位排序,结果为3, 7, 10, 22, 54,此时数组已有序。

vector<int> baseSort(vector<int> &v){
    vector<vector<int>> ctnr(10);//十位数每位范围为0-9
    int n, maxindex, maxnum = 0;//n记录最大数的位数
    for(int i =0; i < v.size(); i++){
        找到位数最长的数字
    }
    for(int f = 10, temp = v[maxindex]; temp > 0; n++){//确定最大数的位数,用n记录
        temp /= f;
    }

    for(int f = 1, times = 0; times < n; f *= 10, times++){
        for(int i =0; i < v.size(); i++){
            ctnr[v[i] /f %10].push_back(v[i]);//将数组中的数字按照位值,类似桶排序放入临时数组的对应下标,如个位模的结果为3,则放入临时数组3下标的位置
        }
        int cnt = 0;
        for(auto i = ctnr.begin(); i != ctnr.end(); i++){
            for(auto j = i->begin(); j != i->end(); ){
                v[cnt++] = *j;//将新排序的数组覆盖原数组
                j = i->erase(j);
            }
        }
    }
    return v;
}

要点:
先要确定最大数有多少位
按位排序记录的数组大小为10
每次新的排序都以上次已经按位排好的结果为基础

归并排序
将多个有序的子区间合并成有序的整个数组,初始将数组分成多个长度为2的子区间,子区间内排序;逐渐增大子区间的长度并排序,直到子区间的长度为整个数组的长度。

void merge(vector<int> &v, int low, int middle, int high){
    int i = low, j = middle+1, k =0;
    vector<int> temp;
    while (i <= middle && j <= high){
		将i与j对应元素中的较小者放入temp数组
	}
	while (i <= middle)//将middle左区间剩余元素加入temp
		temp.push_back(v[i++]);
	while (j <= high)//将middle右区间剩余元素加入temp
		temp.push_back(v[j++]);
	// 将排好序的存回v中low到high该区间内 
	for (i = low, k = 0; i <= high; i++, k++)
		v[i] = temp[k];
}

vector<int> mergeSort(vector<int> &v){
    int n = v.size();
    int size = 1, low, middle, high;//size为区间长度
    while (size <= n){
        low = 0;//low每轮循环初值为0
        while (low + size <= n){
            middle = low + size - 1;
            high = middle + size;
            if(high >= n)//注意单独元素越界问题!!!
                high = n-1;
            merge(v,low,middle,high);
            low = high + 1;
        }
        size *= 2;
    }
}

要点:
middle = low + size - 1,high = middle + size;即middle可与low重合
high不能越界
下一个区间从上一个区间的右边界high的下一个位置开始

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值