剑指OfferC++_21-30+引申:排序

本文深入探讨了数据结构如栈、队列、二叉树、链表的高级应用,覆盖了经典算法如排序、搜索、动态规划的实现与优化。通过实例分析,讲解了如何利用算法解决实际问题,包括路径查找、子数组求和、字符串排列等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

21、栈的压入、弹出序列

22、从上往下打印二叉树 

23、二叉搜索树的后序遍历序列

 24、二叉树中和为某一值的路径

25、复杂链表的复制 

20-25知识点总结:

26、二叉搜索树与双向链表

27、字符串的排列

28、数组中出现次数超过一半的数字

29、最小的K个数

29引申1:选择排序

29引申2:冒泡

29引申3:希尔排序

29引申4:快排

29引申5:归并排序

29引申6:堆排序

29引申7:二叉树排序

29引申6:红黑树

 

30、连续子数组的最大和

25-30知识点总结:


21、栈的压入、弹出序列

第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。

如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

解题思路:模拟进出过程。对于push来说,当他的push队列从左向右碰到第一个和pop相等的时候,就说明要出栈了,
此时就是从当前已经有的push从右往左出栈,所以要判断出栈到哪里。
就看从右往左挨个与pop从左往右判断不等了就说明pop结束,接着向右push

bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        int j=0;
        for(int i=0;i<pushV.size();i++)
        {
            if(pushV[i]==popV[j])//当前Push队列得第一个和pop队列相同就说明要Pop了 ,不同就说明在Push,i++就好了,当前i是几就代表着push到第几个数了           
        {
                pushV.erase(pushV.begin()+i);//开始Pop,push队列删除当前第一个数
                i--;j++;//push队列--,pop队列得j指向下一个要pop的数
                while(i>=0)//只要Push队列还有没pop的就可以循环
                {
                    if(pushV[i]==popV[j])//如果Push与pop的头相同再次pop
                    {
                         pushV.erase(pushV.begin()+i);
                         i--;j++;
                    }
                    else break; //不同就说明pop停止
                }
            }
        }
        if(j!=popV.size())//如果最后push结束了,但是没pop结束说明不对
            return false;
        return true;
    }

22、从上往下打印二叉树 

层次遍历:定注意边界条件!root为NULL的情况

vector<int> PrintFromTopToBottom(TreeNode* root) {
    vector<int> ans;
    if(root==NULL) return ans;
    queue<TreeNode*> tree;
    TreeNode* now=root;
    tree.push(now);
    while(tree.empty()==0)
    {TreeNode* r=tree.front();
     tree.pop();
     ans.push_back(r->val);
     if(r->left!=NULL)  tree.push(r->left);
     if(r->right!=NULL) tree.push(r->right);}
     return ans;
    }

23、二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。

解题思路:后序遍历:左右根。二叉搜索树,左子树上的所有点都小于根节点,而不仅仅是他的左节点小于他!!!
所以通过vector的最后一个值确定根节点,往前找所有比他大的点是右子树,之后剩下前面所有的点都应该比根节点小,如果不小则false。之后得到左子树和右子树的vector,递归看是不是二叉树。     题目中要求! []输出的是false!

bool VerifySquenceOfBST(vector<int> sequence) {
    if(sequence.size()==0) return false;//题中给定[]是false,有一个数是true
    if(sequence.size()==1) return true;
    int root=sequence[sequence.size()-1];//根节点一定是序列的最后一个数
    vector<int> ls,rs;
    int pan=0;//序列前半部分都比root小,后半部分比root大。pan=0。还没找到第一个比root小的数
    for(int i=sequence.size()-2;i>=0;i--){
        if(pan==1){
            if(sequence[i]>root)  return false;//pan=1时发现了一个比root大的数,那么就false
            rs.insert(rs.begin(),sequence[i]);
            continue;}
        if(sequence[i]>root)
            ls.insert(ls.begin(),sequence[i]);
        if(pan==0 && sequence[i]<root) {
            rs.insert(rs.begin(),sequence[i]);
            pan=1; }
    }
    if(rs.size()==0)  return VerifySquenceOfBST(ls);//右子树为空
    if(ls.size()==0)  return VerifySquenceOfBST(rs);
    return VerifySquenceOfBST(rs) && VerifySquenceOfBST(ls);//递归判断左右子树是否满足二叉搜索树
    }

 24、二叉树中和为某一值的路径

输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

解题思路:注意要求最后到叶子节点,所以先判断是叶子节点之后再判断是否相等。

vector<vector<int> > k;//结果
vector<int> kk;//当前路径
vector<vector<int> > FindPath(TreeNode* root,int expectNumber){
    if(root==NULL)   return k;
    if(root->left==NULL && root->right==NULL && root->val==expectNumber)
    {kk.push_back(root->val);
    k.push_back(kk);
    kk.pop_back();
    return k;}
    kk.push_back(root->val);
    FindPath(root->left,expectNumber-root->val);
    FindPath(root->right,expectNumber-root->val);
    kk.pop_back();//一定要pop!因为等于的那个已经存在l里了。而当前这个值已经遍历结束了就不应该出现在kk里
    return k;
}

25、复杂链表的复制 

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

 注意:首先复制意味着地址都不一样。完全复制。之后链表的节点有一个指针指向任一节点。这个节点我们也要指向的是新链表里的新节点。不能指向的是旧链表中的节点。

方法1:O(n)

使用map<旧节点,新节点>先遍历一遍建map,之后再遍历一遍给每一个新节点指向新的任一节点。

    RandomListNode* Clone(RandomListNode* pHead)
    {
    if(pHead==NULL) return NULL;
    map<RandomListNode* , RandomListNode*> k;
    for(RandomListNode* p=pHead;p;p=p->next)
        k[p]=new RandomListNode(p->label);
    for(RandomListNode* p=pHead;p;p=p->next)
    {
        if(p->next) k[p]->next=k[p->next];
        if(p->random) k[p]->random=k[p->random];
    }
    return k[pHead];
    }

 方法2:三步法:

第一步:在原来的链表上,每一个点后面加一个与原来的点相同的点。第二步:给新的点random结点,指向的是前一个结点的下一个结点。第三步:将原来的点去掉

RandomListNode* Clone(RandomListNode* pHead){  
  if(pHead==NULL) return NULL;
  for(RandomListNode* p=pHead;p;p=p->next)
  {
      RandomListNode* next=p->next;
      RandomListNode* fu=new RandomListNode(p->label+10);
      p->next=fu;
      fu->next=next;
      p=p->next;
  }
  for(RandomListNode* p=pHead;p;p=p->next)
  {
      RandomListNode* fu=p->next;
      if(p->random==NULL)  fu->random=NULL;
      else  fu->random=p->random->next;
      p=p->next;
  }
  RandomListNode* head=pHead->next;
  RandomListNode* now=head;
  for(RandomListNode* p=pHead->next;p->next;p=p->next)
  {
     now->next=p->next->next;
     now=now->next;
     if(p->next==NULL) break;
  }
  return head;
    }

20-25知识点总结:

vector相关知识点:

vector<int>::iterator it=find(pushV.begin(),pushV.end(),a);//vector使用find函数
if(it==pushV.end()) return false; //如果不存在find返回pushV.end(),存在返回第一个迭代器
int p=distance(pushV.begin(),it); //使用distance函数找第一个出现的a在vector中的位置,头文件algorithm
vector<int> pushv1(pushV.begin(),pushV.begin()+p);//vector截取只能让他们先保存在一个新的vector里来截取

pushv1.pop_back() 退出  vector<vector<int> >不能>>会报错

 

26、二叉搜索树与双向链表

将二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解题方法: 对于二叉树他的中序遍历就是排序,题中的要求是不能创建新的节点,只能调整指针节点的指向
方法1:深度遍历,一直保存当前得到的左边顺序序列的最右侧节点的指针lr。
       对于根节点r,左子树遍历得到的序列,返回的是这个序列最左侧的节点ll。
       其中遇到叶子节点,lr=该节点,并且返回这个节点。
       这个时候lr->right=root。root->left并且root成为新的lr。
       之后遍历右边的子树,返回的也是右边子树的最左侧的节点,rl。 root->right=rl。rl->left=root
方法2:正常的深度遍历,但是传pre的地址,这样遍历到最左边的数开始。
        pre 代表的存的是排序之后的这个节点的上一个节点。所以到找到最左边的点之前pre都是NULL。所以pre应该传地址,pre的顺序是和cout的顺序相同(cur ,pre)如果遇到cur是空,那么返回。并且让当前的cur->left=NULL.如果pre不空,就让pre->left是cur。cur->right是pre。之后让pre=cur, 调用右子树。

//用leftLast保存当前保存的链表最右侧,返回值是当前保存的链表的最左侧
TreeNode* leftLast = NULL;
TreeNode* Convert(TreeNode* pRootOfTree) {//方法1:递归的方法!
    if(pRootOfTree==NULL)  return NULL;
    if(pRootOfTree->right==NULL && pRootOfTree->left==NULL) {
        leftLast=pRootOfTree;
        return pRootOfTree; }
    TreeNode* left=NULL;
    if(pRootOfTree->left!=NULL){
         left=Convert(pRootOfTree->left);//左子树与根节点相关的是leftLast,
         leftLast->right=pRootOfTree;
         pRootOfTree->left=leftLast;;}
    leftLast=pRootOfTree;
    if(pRootOfTree->right!=NULL){
        TreeNode* right=Convert(pRootOfTree->right);//右子树与根节点有关的是返回的点
        pRootOfTree->right=right;  //不能写leftLast->right=right因为此时,的leftLast就是右子树的最右边的节点了。
        right->left=pRootOfTree; }
    return left!=NULL?left:pRootOfTree;  //返回的是这个链表的最左边的点,如果当前left是空意味着当前节点左节点是空,那么整个链表最左边就是当前节点
     }
void convertHelper(TreeNode* cur, TreeNode*& pre) {//注意!需要传地址
     if(cur == NULL) return;
     convertHelper(cur ->left, pre);
     cur ->left = pre;
     if(pre) pre ->right = cur;
     pre = cur;
     convertHelper(cur ->right, pre);         
 }
TreeNode* Convert(TreeNode* pRootOfTree) {//方法2
      if(pRootOfTree == NULL) return NULL;
      TreeNode* pre = NULL;
      convertHelper(pRootOfTree, pre);
      TreeNode* res = pRootOfTree;
       while(res ->left) res = res ->left;
       return res;        
}

27、字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。

例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

方法1:

【例】 如何得到346987521的下一个
    1,从尾部往前找第一个P(i-1) < P(i)的位置
            3 4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
        最终找到6是第一个变小的数字,记录下6的位置i-1
    2,从i位置往后找到最后一个大于6的数
            3 4 6 -> 9 -> 8 -> 7 5 2 1
        最终找到7的位置,记录位置为m
    3,交换位置i-1和m的值
            3 4 7 9 8 6 5 2 1
    4,倒序i位置后的所有数据
            3 4 7 1 2 5 6 8 9
    则347125689为346987521的下一个排列


方法2:使用从第一个开始得到最后一个数的方法。

方法3:为了得到的顺序是字典顺序,需要将输入的字符串去重,并且排序。之后使用冒泡的想法得到结果

string sswap(string str,int i,int j)
     {
         char k=str[i];
         str[i]=str[j];
         str[j]=k;
         return str;
     }

vector<string> Permutation(string str)
{
    vector<string> kk;
    if(str.empty()) return kk;
    kk.push_back(str); //初始字符串是本身,要求是从小到大排序的
    while(1)
    {
        int i=str.length()-1;
        while(i>0)
        {
            if(str[i]>str[i-1])  //对于当前字符串,从后往前找第一个变小的哪个数字g,
                break;
                i--;
        }
        if(i==0)
            break;
        int j=i;
        int y=j;
        for(;j<str.length();j++)//从变小的那两个数中小的哪个数g-1开始,从左往右遍历,找最后一个比他大的数d
            if(str[j]>str[i-1])
             y=j;
        str=sswap(str,i-1,y);//将小的哪个数g-1和大的那个数d交换。
        reverse(str.begin()+i,str.end());//从g位置开始往右所有的数,颠倒。得到下一个数
        kk.push_back(str);
    }
    return kk;
}
void swap(char &fir,char &sec)  {
            char temp = fir;
            fir = sec;
            sec = temp;    }

void Permutation(string str,vector<string> &result,int begin)    {
        if(begin == str.size()-1)    // 递归结束条件:索引已经指向str最后一个元素时
            {
                if(find(result.begin(),result.end(),str) == result.end())
                {result.push_back(str);}
            }
                else  // 第一次循环i与begin相等,相当于第一个位置自身交换,关键在于之后的循环,
  // 之后i != begin,则会交换两个不同位置上的字符,直到begin==str.size()-1,进行输出;
                {for(int i=begin;i<str.size();++i)
                {
                    swap(str[i],str[begin]);
                    Permutation(str,result,begin+1);
                    swap(str[i],str[begin]);   // 复位,用以恢复之前字符串顺序,达到第一位依次跟其他位交换的目的
}
}
}

vector<string> Permutation(string str)     {  //方法2
    vector<string> result;
    if(str.empty()) return result;
    Permutation(str,result,0);
    // 此时得到的result中排列并不是字典顺序,可以单独再排下序
    sort(result.begin(),result.end());
    return result;    }


28、数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解题思路:排序之后出现超过长度一半的那个数字一定出现在数组的最中间,因此找最中间的那个数出现多少次,如果多于一半就输出

int MoreThanHalfNum_Solution(vector<int> numbers) {
    if(numbers.size()==0)   cout<<0;
    if(numbers.size()==1)   cout<<numbers[0];
    sort(numbers.begin(),numbers.end());
    int y=0;
    for(int i=0;i<numbers.size();i++)
       if(numbers[i]==numbers[numbers.size()/2])   y++;
    if(y>numbers.size()/2)  return numbers[numbers.size()/2];
    else  return 0;  }

29、最小的K个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

方法1:使用sort排序,默认是快排,时间复杂度为O(nlogn)

方法2:使用min_element函数

vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
      if(input.size()<k) {
         vector<int> kk;
         return kk;  }
    sort(input.begin(),input.end());
    vector<int> kk(input.begin(),input.begin()+k);
    return kk;
    }
int small(vector<int> input){
    int num=distance(input.begin(), min_element(input.begin(),input.end()));
    return num;}

29引申1:选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,表现最稳定的排序算法之一,最佳情况:T(n) = O(n2)  最差情况:T(n) = O(n2)  平均情况:T(n) = O(n2)

vector<int> GetLeastNumbers_Solution_xuanze(vector<int> input){
   for(int i=0;i<input.size();i++){
       vector<int> l(input.begin()+i,input.end());
       int y=small(l)+i;
       int u=input[i];
       input[i]=input[y];
       input[y]=u; }
   return input;}

29引申2:冒泡

先固定最大的值,第二个循环固定第二大的值。以此类推

最佳情况:T(n) = O(n)   最差情况:T(n) = O(n2)   平均情况:T(n) = O(n2)

vector<int> GetLeastNumbers_Solution_maopao(vector<int> input, int k){ 
    for(int i=0;i<input.size();i++)
        for(int j=0;j<input.size()-i-1;j++) {
        if(input[j+1]<input[j]) {
            int u;
            u=input[j];
            input[j]=input[j+1];
            input[j+1]=u;        }           }
    return input;
}

29引申3:希尔排序

希尔排序是首先按照下标进行分组,分组方法:按照下标以某一增量increment,来分组,下标对increment取余,余数相同的一组。对每组使用直接插入排序算法排序。每组排序之后,讲增量以一定的策略减少。随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
 增量初始和减少策略:最开始提出的时候是len/2,但是这样奇数位都和奇数位置判断,效率低,所以提出初始增量是len/3+1之后每次减少是increment=increment/3+1
 希尔排序是不稳定的选择排序。原始序列里有两个5,原来在前面的5很可能排序之后到后面了。所以如果对于一个struct。我们重载struct里面一个参数来进行shell排序,那么相对位置就会改变

vector<int> GetLeastNumbers_Solution_shell(vector<int> input)
{   int increment=floor(input.size()/3+1);
    while(increment>1)
    {
        for(int i=0+increment;i<input.size();i++)//这样就不用单独判断不能被increment整除的部分了
        {
                int temp=input[i];
                int j=i;
                while(j-increment>=0 && input[j-increment]>temp){
  //对于shell当前的这个子序列来说,是插入排序,在排到j的时候,j前面都已经排号序列了。所以当前的j,直接往前判断,找到比自己小的停止。后面的都依次往后移一个
                input[j]=input[j-increment];
                j-=increment;}
                input[j]=temp;
        }
        increment=increment/3+1;
    }
   return input;}

29引申4:快排

快速排序算法是一种基于交换的高效的排序算法,它采用了分治法的思想:
1、从数列中取出一个数作为基准数(枢轴,pivot)。 当前代码找的是每个子序列的第一个数
2、将数组进行划分(partition),将比基准数大的元素都移至枢轴右边,将小于等于基准数的元素都移至枢轴左边。
3、再对左右的子区间重复第二步的划分操作,直至每个子区间只有一个元素。
快速排序时间复杂度:快速排序的时间复杂度在最坏情况下是O(N2),平均的时间复杂度是O(N*lgN)。
假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),需要遍历多少次呢?至少lg(N+1)次,最多N次。

为什么最少是lg(N+1)次?快速排序是采用的分治法进行遍历的,我们将它看作一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。因此,快速排序的遍历次数最少是lg(N+1)次。
为什么最多是N次?这个应该非常简单,还是将快速排序看作一棵二叉树,它的深度最大是N。因此,快读排序的遍历次数最多是N次。
快速排序稳定性:快速排序是不稳定的算法,它不满足稳定算法的定义。
算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!

void partition_my(vector<int>& input,int left,int right)
{
    if(left>=right)
        return;
    int i=left; int j=right+1;
    while(true){
        while( input[++i]<input[left]&&i<right)  ;
        while(  input[--j]>input[left]&& j>left)  ;
            if(i<j){
        swap(input[i],input[j]);
           }
           else break; }
    swap(input[left],input[j]);  //注意swap的使用
    partition_my(input,left,j-1);
    partition_my(input,j+1,right);

}

29引申5:归并排序


两路归并排序(Merge Sort),也就是我们常说的归并排序,也叫合并排序。它是建立在归并操作上的一种有效的排序算法,归并操作即将两个已经排序的序列合并成一个序列的操作。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
1.申请两个与已经排序序列相同大小的空间,并将两个序列拷贝其中;
2.设定最初位置分别为两个已经拷贝排序序列的起始位置,比较两个序列元素的大小,依次选择相对小的元素放到原始序列;
3.重复2直到某一拷贝序列全部放入原始序列,将另一个序列剩下的所有元素直接复制到原始序列尾。

最差时间复杂度:O(nlogn)  平均时间复杂度:O(nlogn) 最差空间复杂度:O(n)  稳定性:稳定

网上归并有两种:

归并1:    2的倍数开始排序。
排0、1  2、3 4、5 6
排0,1,2,3 4,5,6
排0.1.2.3.4.5.6
归并2:
7/2=3 || 3      4
3/2=1 || 1 2    2    2
2/1=1 || 1 1 1  1 1  1 1
排0 、 1,2 、 3,4 5,6
排 0,1,2 3,4,5,6
排 0,1,2,3,4,5,6
 

void pin(vector<int>& input,int left,int right,int num)
{
    int a1=left; int a2=left+num;
    vector<int> ans;
    while(a1<left+num && a2<right )
    {
        if(input[a1]<input[a2]) {ans.push_back(input[a1]); a1++;}
        else {ans.push_back(input[a2]); a2++;}
    }
    while(a1<left+num) {ans.push_back(input[a1]); a1++;}
    while(a2<right) {ans.push_back(input[a2]); a2++;}
    for(int i=0;i<ans.size();i++) input[left+i]=ans[i];

}

void merge(vector<int>& input)//第一种,2的倍数的归并方法
{
    int i=2;
    for(;i/2<input.size();i*=2)
    {
        int j=0;
        for(;j+i<input.size();j+=i)//这样可以不求log,而判断循环结束
            pin(input,j,j+i,i/2);
        pin(input,j,input.size(),i/2);
    }
}
vector<int> he(vector<int> l,vector<int> r)
{
     vector<int> ans;
     int i=0,j=0;
    while(i<l.size() && j<r.size() )
    {
        if(l[i]<r[j]) ans.push_back(l[i++]);
        else ans.push_back(r[j++]);
    }
    while(i<l.size()) ans.push_back(l[i++]);
    while(j<r.size()) ans.push_back(r[j++]);
  return ans;

}
vector<int> merge2(vector<int>& input)//第二种除以二的方式
{
    if(input.size()==1) return input;
     int a=input.size()/2;
     vector<int> l(input.begin(),input.begin()+input.size()/2);
     vector<int> r(input.begin()+input.size()/2,input.end());
     l=merge2(l);
     r=merge2(r);
     return he(l,r);

}

 

29引申6:堆排序

堆排序过程详见https://blog.youkuaiyun.com/qq_33666011/article/details/91849233

如果要增序排序,首先将现在的序列制成大堆的形式。之后每次将大堆的top拿出来,放在当前堆的最末尾,就可以递归得到最大的,倒数第二大的,等等。

C++里有两种实现堆的方法:1)使用stl  2)自己用数组写堆

 void heap_stl(vector<int>&input)
 {
   make_heap(input.begin(),input.end(),less<int>());//大堆
   //make_heap(input.begin(),input.end(),greater<int>());//小堆
   sort_heap(input.begin(),input.end());
//一定先make之后再sort,且make是做成大堆,sort出来的是增序。降序需要make和sort的第三个元素都是greater<int>()
   //sort_heap(&input[0],&input[input.size()-1]);//一定使用begin,不能使用[]

 }

 

void heap_adjust(vector<int>&input,int start,int over)//保证从start到over每一个节点都满足是大堆结构
{
    int now=start;//now指向的是当前要被换的那个节点。
    int temp=input[now];
    while(now*2<=over)
    {
        int j=now*2;
        if(j+1<=over && input[j]<input[j+1])
            j++;
        if(input[j]>temp)
//注意这里是大于temp。而不是大于temp[now]因为当时的tenp[now]已经不是j的父节点的值了!,J的父节点一直都是temp!
        {
            input[now]=input[j];
            now=j;
        }
        else break; 
//如果当前的子节点不比当前节点大,那就不用换了,而下面的节点因为已经排好,肯定是大堆,所以不用考虑了
    }
    input[now]=temp;//最后一个now就是等于temp
}
void heap( vector<int>&input )
//首先为了方便完全二叉树的计算,在最前面加一位占位
//之后构造一个大堆,这样当前inut[1]就是整个序列最大的数。把他与最后一个数调换,因为最大的数要放在最后。
//之后将除了最后一个数的剩余序列再一次构造一个大堆
{
    input.insert(input.begin(),0);
//在完全二叉树中,想要节点i的子节点是2*i和2*i+1,并且第一个有子节点的节点位于size()/2,需要节点第一个位置编号是1.所以在vector前面加上一个占位的0.
//在排序的所有过程中,都是从序号1开始处理的,所以没关系
    for(int i=0;i<input.size()/2;i++)
    //得到大堆,要求:每一个节点的父节点都大于他们的子节点。
//需要从最后一个有子节点的节点开始看,看从他开始的所有节点是否满足节点的大小大于叶子节点的大小。
//不满足的话。将他的叶子节点中大的那个和该节点的值互换。
    //所以要从最后一个有子节点的叶子节点开始进入heap_adjust。并且在heap_adjust里要从这个节点开始到最有一个有叶子节点的那个点,都要判别一遍。
    //第一个有子节点的点在堆里是size()/2那个点,(注意这里的size是真正排序的Len+1)因为堆是完全二叉树
    heap_adjust(input,input.size()/2-i,input.size()-1);
    for(int i=1;i<input.size();i++)
    {
        swap(input[1],input[input.size()-i]);
        heap_adjust(input,1,input.size()-i-1);
    }
    input.erase(input.begin());
}

29引申7:基数排序

基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

先找这些数里的最大值,之后看最大值是几位的。之后1. 按照个位数进行排序。2. 按照十位数进行排序。3. ......。
排序后,数列就变成了一个有序序列。

对于给定的n个d位数,取值范围为[0,k],我们使用计数排序比较元素的每一位,基数排序耗时Θ(n+k),那么基数排序的复杂度为Θ(d(n+k))。O( d(2*n+20) ) 

void countsort(vector<int>& a,int exp){
    vector<int> tmp(a.size());
    int bucket[20]={0};//20是考虑了负数的情况
    for(int i=0;i<a.size();i++)
        bucket[(a[i]/exp)%10+10]++;
    for(int i=1;i<20;i++)
        bucket[i]+=bucket[i-1];//bucket[i]其实就是对应最后第一个exp位是i的那个数的真实位置
        // 之后倒叙!遍历到的第一个exp位是i的那个数的新位置就是bucket[i],之后bucket[i]--
    for(int i=a.size()-1;i>=0;i--){
        tmp[bucket[(a[i]/exp)%10+10]-1]=a[i];//a[i]的新位置是bucket[(a[i]/exp)%10]-1 //不要忘记-1!!!
        bucket[(a[i]/exp)%10+10]--;
    }
    swap(a,tmp);
}


void redixsort(vector<int> a){
    int max=*max_element(a.begin(),a.end());
    int exp=0,maxx=max;
    while (max){max=max/10;exp++;}
    for(exp=1;maxx/exp>0;exp*=10){
        countsort(a,exp);
    }
    for(vector<int>::iterator it=a.begin();it!=a.end();it++)
        cout<<*it<<" ";
}

29引申8: 桶(箱)排序

基数排序:根据键值的每位数字来分配桶;

计数排序:每个桶只存储单一键值;O(2*n),装桶一遍,连桶一遍.

桶排序:每个桶存储一定范围的数值;同一桶内是插入排序.O(m*(n/m))+O(m)=O(n+m)m是桶的数量

桶排序:当输入的数据可以均匀的分配到每一个桶中,最快;当输入的数据被分配到了同一个桶中,最慢

设置若干个桶,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字在某个范围内的记录全都装入到第k个桶里(分配),然后按序号依次将各非空的桶首尾连接起来(收集)。

29引申9:AVL

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

struct treenode
{
    int key;
    int height;
    treenode* left=NULL;
    treenode* right=NULL;
    treenode(int v)
    {
        key=v;
    }
};

int height(treenode* tree)
{
    if (tree != NULL)
        return tree->height;
    return 0;
}

treenode* leftLeftRotation(treenode* k2)
{
    treenode* k1;

    k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;

    k2->height = max( height(k2->left), height(k2->right)) + 1;
    k1->height = max( height(k1->left), k2->height) + 1;

    return k1;
}

treenode* rightRightRotation(treenode* k1)
{
    treenode* k2;

    k2 = k1->right;
    k1->right = k2->left;
    k2->left = k1;

    k1->height = max( height(k1->left), height(k1->right)) + 1;
    k2->height = max( height(k2->right), k1->height) + 1;

    return k2;
}

treenode* leftRightRotation(treenode* k3)
{
    k3->left = rightRightRotation(k3->left);

    return leftLeftRotation(k3);
}

treenode* rightLeftRotation(treenode* k1)
{
    k1->right = leftLeftRotation(k1->right);

    return rightRightRotation(k1);
}

treenode* treeinsert(treenode* tree, int key)
{
    if (tree == NULL)
    {
        // 新建节点
        tree = new treenode(key);
        if (tree==NULL)
        {
            cout << "ERROR: create avltree node failed!" << endl;
            return NULL;
        }
    }
    else if(tree->key==-1)
    {
        tree->key=key;
    }
    else if (key < tree->key) // 应该将key插入到"tree的左子树"的情况
    {
        tree->left = treeinsert(tree->left, key);
        // 插入节点后,若AVL树失去平衡,则进行相应的调节。
        if (height(tree->left) - height(tree->right) == 2)
        {
            if (key < tree->left->key)
                tree = leftLeftRotation(tree);
            else
                tree = leftRightRotation(tree);
        }
    }
    else if (key > tree->key) // 应该将key插入到"tree的右子树"的情况
    {
        tree->right = treeinsert(tree->right, key);
        // 插入节点后,若AVL树失去平衡,则进行相应的调节。
        if (height(tree->right) - height(tree->left) == 2)
        {
            if (key > tree->right->key)
                tree = rightRightRotation(tree);
            else
                tree = rightLeftRotation(tree);
        }
    }
    else //key == tree->key)
    {
        cout << "添加失败:不允许添加相同的节点!" << endl;
    }

    tree->height = max( height(tree->left), height(tree->right)) + 1;

    return tree;
}

void show(treenode* roott)
{
    if(roott==NULL) return;
    show(roott->left);
    cout<<"key :  "<<roott->key<<" value :  "<<height(roott)<<endl;
    show(roott->right);
}


void countsort(vector<int>& a,int exp){
    vector<int> tmp(a.size());
    int bucket[20]={0};//20是考虑了负数的情况
    for(int i=0;i<a.size();i++)
        bucket[(a[i]/exp)%10+10]++;
    for(int i=1;i<20;i++)
        bucket[i]+=bucket[i-1];//bucket[i]其实就是对应最后第一个exp位是i的那个数的真实位置
        // 之后倒叙!遍历到的第一个exp位是i的那个数的新位置就是bucket[i],之后bucket[i]--
    for(int i=a.size()-1;i>=0;i--){
        tmp[bucket[(a[i]/exp)%10+10]-1]=a[i];//a[i]的新位置是bucket[(a[i]/exp)%10]-1 //不要忘记-1!!!
        bucket[(a[i]/exp)%10+10]--;
    }
    swap(a,tmp);
}

void redixsort(vector<int> a){
    int max=*max_element(a.begin(),a.end());
    int exp=0,maxx=max;
    while (max){max=max/10;exp++;}
    for(exp=1;maxx/exp>0;exp*=10){
        countsort(a,exp);
    }
    for(vector<int>::iterator it=a.begin();it!=a.end();it++)
        cout<<*it<<" ";
}

int main()
{
        freopen("../a.txt","r",stdin);
        treenode* root=NULL;//整个树的根节点
        int now;
        while(cin>>now)root=treeinsert(root,now);
        show(root);
        cout<<"root is "<<root->key;
        return 0;

}

 

29引申10:红黑树

 

30、连续子数组的最大和

计算连续子向量的最大和,例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8。返回它的最大连续子序列的和,

方法1:

方法2:

int FindGreatestSumOfSubArray_(vector<int> array1) {//方法1
    int ans=-1;
    int maxans=-2147483647-1;
    for(int i=0;i<array1.size();i++)
    {
        if(ans==-1 && array1[i]<0){ 
             if(maxans<array1[i])  maxans=array1[i];
             continue; }
        if(ans==-1 && array1[i]>=0 ) ans=0;
        ans=ans+array1[i];
        if(ans<0)       ans=0;
        if(ans>maxans)  maxans=ans; }
    return maxans;
    }
int FindGreatestSumOfSubArray(vector<int> array1) {//方法2:动态规划
    if(array1.size()==0)  return 0;
    vector<int> df;//存的是在第i个之前所有的
    df.push_back(array1[0]);
    for(int i=1;i<array1.size();i++)
        df.push_back(array1[i]+df[i-1]>array1[i]?df[i-1]+array1[i]:array1[i]);
    return df[distance(df.begin(),max_element(df.begin(),df.end()))];
    }

25-30知识点总结:

1)回顾了几种排序方法

2)string用法:
截取: string i=a.substr(i,j)  [i,j)  // i=i+i[i.length()-1]

3)max_element与,min_element

df[distance(df.begin(),max_element(df.begin(),df.end()))]

4)stl里堆的用法

int myints[] = {10,20,30,5,15};
 vector<int> v(myints,myints+5);

make_heap (v.begin(),v.end());

pop_heap (v.begin(),v.end()); v.pop_back();

 v.push_back(99); std::push_heap (v.begin(),v.end());

sort_heap (v.begin(),v.end());

reverse(myvector.begin(),myvector.end());

5)swap(input[left],input[j]);


(1)用法1:释放内存

vector<double>v;

vector<double>().swap(v);    //也可以是v.swap(vector<double>());

(2)用法二:修整容器的空间

vector<int>(vec).swap(vec); // 或者 ivec.swap(vector<int>(ivec));

clear函数并不能完全保证内存回收,并不是所有的STL容器的clear成员函数的行为都和vector一样。
事实上,其他容器的clear成员函数都会释放其内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值