第【3】章: 查找与排序下 学习报告

 

 

 

(3.1)节:   分治思想   

 

 

(3.2)节:  快速排序

 

快排的思想主要在划分,归并的思想主要在合并 ,快速排序在划分的同时将较小的数放在左边,较大的数放在右边,在不断的递归划分,最后不知不觉就排序好了

(3.3)节:  快速排序 单向扫描分区法

1、题干:单项扫描分区法  

2、解答思路: 起始位置为主元,比主元大的放后面,最后把主元放在分好的中间

3、关键代码:

int one(int a[],int q,int n)//q为起始位置,n为终点

{int inter=a[q];//把起始位置作为主元

int R=q+1;

int L=n;

int b;

while(R<=L)///数组都遍历完了即R与L交错时停止循环

         {  if(inter>=a[R])     R++;

                   else           {b=a[R];a[R]=a[L];a[L]=b;///交换位置,把比主元大的放后面

                                               L--;}

         }

        a[q]=a[L];a[L]=inter;///交换位置把主元放进去作为间隔

        return  L;

}

void en (int a[],int q,int n)  //快速排序的递归,n=数组的最大下标

{        if(q<n){

int c=one(a,q,n);

en(a,q,c-1);

en(a,c+1,n);      }

}

(3.4)节:  快速排序 双向扫描分区法

1、题干

 

2、解答思路:定主元后从两端同时进行与主元比较大小,大的在后面,小的在前面

3、关键代码:

int two(int a[],int q,int n)//q为起始位置,n为终点

{int inter=a[q];//把起始位置作为主元

int L=q+1;

int R=n;

int b;

while(L<=R)///数组都遍历完了即R与L交错时停止循环

                   {

                   while(L<=R&&inter>=a[L])       L++;

                   while(L<=R&&inter<a[R])        R--;

                   if(L<R)

                   {b=a[L]; a[L]=a[R]; a[R]=b;}///交换位置,把比主元大的放后面 ,比主元小的放前面

}

        a[q]=a[R];a[R]=inter;///交换位置把主元放进去作为间隔

        return  R;

}

void en (int a[],int q,int n)  //快速排序的递归,n=数组的最大下标

{        if(q<n){

int c=two(a,q,n);

en(a,q,c-1);

en(a,c+1,n);      }

(3.5)节:  快速排序 有相同元素的三指针分区法

1、题干:有相同元素的三指针分区法

2、解答思路:与单向分区类似,多了一个指针来操作与主元相同的元素

3、关键代码:

struct s

{int be;

int end;};

s one(int a[],int q,int n)//q为起始位置,n为终点

{int inter=a[q];//把起始位置作为主元

int R=q+1;

int I=R;//相同项的首位置

int L=n;

int b;

s s1;///结构用于储存相同变量的两端位置

while(R<=L)///数组都遍历完了即R与L交错时停止循环

         {  if(inter>a[R])

                   {b=a[I];a[I]=a[R];a[R]=b;//交换位置使比主元小的都在左边

                   R++;I++;}

                   else if(inter==a[R])            R++;//如果与主元相同则I不动,R继续右移

                   else  {b=a[R];a[R]=a[L];a[L]=b;///交换位置,把比主元大的放后面

                                               L--;}

         }

        a[q]=a[I-1];a[I-1]=inter;///交换位置把主元与相同项初边界的前一个值交换位置

        s1.be=I-1;///把边界存在结构里面

        s1.end=L;

         return  s1;

}

(3.6)节:快速排序的优化——主元的取值

优化应能,避免所取主元过大或过小

1、  三点中值法:取开头,结尾和中间的值进行比较,取“中间值”,然后把中间值与开头调换位置。然后再进行划区间和递归

2、  绝对中值法:对原数组进行5个一组分组,最后一组可能没有5个;然后对各组进行插入排序,获取其中间值,再将所有中间值组成一个组,再插入排序再获取中间值。此时该中间值作为主元与第一个元素调换位置,再进行划区间与排序。

3、  在工程实践中当数量较少时,即少于8个使用插入排序性能更好

(3.7)节:分治模式的完美诠释——归并排序

1、题干:归并排序

2、解答思路:将数组划分成两个部分,通过递归不断划分,当只有一个元素时停止划分,然后将两个小组排序后归并在一起。

3、关键代码

void text(int a[],int be,int end)

{

         int p=be,t=be;//p为初始位置

int mid=be+((end-be)>>1);//找出中值下标

int m=mid+1;//右边第一个值

if(be>=end)//如果只有一个元素则开始返回

return;

         text(a,be,mid);//左边

         text(a,mid+1,end);//右边      

int help[end+1];

for(int i=0;i<=end;i++)

help[i]=a[i];//创建相同大小的数组,并复制

while(p<=mid&&m<=end)//左右两边通过比较归并在一起

 {

if(help[p]<=help[m])

 {a[t]=help[p] ;p++;t++;}

 else

 {a[t]=help[m];m++;t++;}

 }

 while(p<=mid)///当左边还有数时追加,

 {a[t]=help[p] ;p++;t++;}

return;

}

(3.8)节:解题

 

 

 

 

 

(3.9)节:K个元素

1、题干

 

2、解答思路:求出主元的位置,如果等于K则该元素值为主元,如果小于k则向右递归,大于K则向左递归。

3、关键代码

int text(int a[],int be,int end,int k)//be 起始下标,end结尾坐标,第 k的元素

{        int mid=be+((end-be)>>1);

int b;

//////三点中值法第一个为主元

if( (a[be]>a[mid]&&a[mid]>a[end] )|| (a[be]<a[mid]&&a[mid]<a[be]) )

{b=a[be];a[be]=a[mid];a[mid]=b;}

else if((a[be]>a[end]&&a[end]>a[mid]) || (a[be]<a[end]&&a[end]<a[mid]))

 {b=a[be];a[be]=a[end];a[end]=b;}

//////单向扫描法确定主元的位置

int p=be+1;

int d=end;

while(p<=d)

{        if(a[p]<=a[be])

                   p++;

         else

                  {b=a[d];a[d]=a[p];a[p]=b; d--;}//pd交换位置

}

 {b=a[be];a[be]=a[d];a[d]=b;}//主元和d交换位置,现在d为主元的位置

 //主元位置与k的关系

int t=d+1;

 if(t==k)

        return a[d];

 else if(k>t)

 return text(a,d+1,end,k);

 else

return text(a,be,d-1,k);

}

(3.10)节:超过一半的数字

1、题干

 

2、解答思路:因为该数字的个数超过元素的一半,所以排序后该数组最中间元素必定是该数字。也可以将上面代码改成查找第n/2的值。也可一将数组两个两个分组,不相同的就消掉,相同的就留下来。

3、关键代码

int text(int a[],int end)//end结尾坐标,

{

         int d=a[0];

         int n=1;

///最后剩下的就是超过一半的值了

         for(int i=1;i<=end;i++)

{        if(n==0)

                   {d=a[i];continue;}//如果不相等则抵消,从下一个开始

         if(a[i]==d)          n++;

         else          n--;

}

         return d; ///返回超过一半的值   

}

(3.11)节:寻找发帖水王

1、题干:

2、解答思路:上一题的拓展,基本一样。

拓展:如果刚好一半,那么则可以多家一个动作,每一个数都和最后面那一个数比较并计数,如果等于一半那么这个数刚好就是所求值。如果不等于则可以将它去掉,此时有大于一半了,再使用两两消除法。

(3.12)节:最小可用ID

1、题干:

 

2、解答分析: 先将非负数的乱序数组排好序,再将比较下标+1与值是否相等,第一个不相等的下标就是最小id。(nlgn+n)

也可以建立一个辅助数组全部值都为0,将原数组的值为辅助数组的下标,将辅助数组相对应的位置赋予1,最后循环辅助数组,从help[1]开始寻找第一个为0的值,返回其下标。(n+n)

3、关键代码:

int text(int a[],int end)//end结尾坐标,

{

         int help[end+2];

         for(int i=0;i<=end;i++)

                   if(a[i]>end)///保证不超过边界

                            continue;

                   else

                             help[a[i+1]]=1;

         for(int i=1;i<=end+1;i++)

                    if(help[i]!=1)

                    return i;

                    return end+1;

}

(3.13)节:最小可用ID(下)

1、题干:如果不能使用辅助空间

2、解答思路:除了排好顺序后查找,我们还可以使用分区,确定主元坐标是否正确,等于的话,那么左边是紧密的,最小ID应该在主元右边,小于的话在主元的左边。不可能大于。

3、关键代码

int text(int a[],int be,int end)//end结尾坐标,

{

if(be>end)///左右交错时返回

return be+1;

int mid=be+((end-be)>>1);

int b;

//////三点中值法第一个为主元

if( (a[be]>a[mid]&&a[mid]>a[end] )|| (a[be]<a[mid]&&a[mid]<a[be]) )

{b=a[be];a[be]=a[mid];a[mid]=b;}

else if((a[be]>a[end]&&a[end]>a[mid]) || (a[be]<a[end]&&a[end]<a[mid]))

 {b=a[be];a[be]=a[end];a[end]=b;}

//////单向扫描法确定主元的位置

int p=be+1;

int d=end;

while(p<=d)

{        if(a[p]<=a[be])

                   p++;

         else

                   {b=a[d];a[d]=a[p];a[p]=b; d--;}//pd交换位置

}

 {b=a[be];a[be]=a[d];a[d]=b;}//主元和d交换位置,现在d为主元的位置

 //主元位置左边是否紧密

if(a[d]==d+1)

return text(a,d+1,end);

else

return text(a,be,d-1);

}

 (3.14)节:  关于逆序对  

1、题干

 

2、解答思路:1、由于两个数组已经是有序的,且A的大小能融入B。首先确认A和B的真正个数+1,作为A数组的下标开始,原数组最后一个开始比较,大的放进去最后面从后面开始排序,最终确认A

                            2、利用归并排序的思路,只需要在排序时候对左大于右的进行计数

3、关键代码

int count=0; //计数

int text(int a[],int be,int end)//end结尾坐标,

{

int mid=be+((end-be)>>1);

if(be>=end)

return count;

text(a,be,mid);

text(a,mid+1,end);

int help[end+1];

for(int i=0;i<=end;i++)

help[i]=a[i];///创建辅助空间

int t=be;//辅助数组起始

int p=t;//数组起始指针

int d=end;//辅助数组结尾指针

int m=mid+1;//辅助数组第二个数组的起始指针

while(m<=end&&t<=mid)

if(help[t]<=help[m])///左边进

         {a[p]=help[t];t++;p++;}

         else///右边进

         {a[p]=help[m];p++;m++;count=count+mid-t+1;}///比归并排序多的

while(t<=mid)//左边剩继续进

         {a[p]=help[t];t++;p++;}

         return count;

 }

(3.15)节:  树、二叉树的概念  

1、二叉树用数组表示:根节点下标为n=0;左子树为2n+1,右子树为2n+2;求父节点则为(n-1)/2

2、先序二叉树的代码:

Void out(int a[],int i)///i为根点的

{

If( i>= (sizeof(a)/sizeof(a[0]) )

         Return ;

Cout<<a[i];//打印根节点

         out(a,2*i+1);///左子树

         out(a,2*i+2);///右子树

}

(3.16

(3.17

(3.18)节:  堆概念、堆排序的思路  

1、题干:

 

 

2、解答思路:小顶堆将数组搞成堆结构,类似于二叉树,然后从最后最小的一个堆n/2-1开始,到根节点0,不断选择最小值作为父亲,最后根节点为数组的最小值。将最小值与最后面元素进行调换,然后长度减1进行循环,重复上面内容,直到长度小于0。将得到一个从大到小的数组。大顶堆则得到一个从小到大的数组,堆排序比较少用到,主要是快速排序                   建堆nlgn+排序n=nlgn

3、关键代码:

void text(int a[],int k,int  len) ///////////以k为根节点建立小顶堆

{       

                  int L=2*k+1;//左孩子

                  int R=2*k+2;//右孩子

                  int b;//调换位置辅助空间

//边界     

if(L>=len)///L越界就返回

                  return;

//找最小孩子

int min=L;

if(R>=len) //当执行这个语句时,L是最后没有越界,R越界 ,L为最小值

                  min=L;

else

                  {        if(a[L]>a[R])

                            min=R;}

//最小孩子与父亲比较          

if(a[min]>=a[k])//k最小,不用调换

                  return;

else

{b=a[min];a[min]=a[k];a[k]=b;}

//孩子的值已经变化,以改变孩子为根

text(a,min,len) ;      

}

////////////////////////////////////////建立小顶堆堆

void tr(int a[],int  len)

{        int b;

for(int r=len-1;r>=0;r--)///不断选择最小值与最后颠换

{

for(int i=len/2-1;i>=0;i--) ///遍历所有父亲根节点,根节点得到最小值

text(a,i,r+1);

b=a[0];a[0]=a[r];a[r]=b;///根节点于最后元素调换位置

}

}

(3.19计数排序

1、题干:

 

2、解答思路:创建一个至少和原数组表示最大值一样大小的空间,将数组的值作为下标,新数组的值表示原数组中出现的个数,然后再将下标返回给原数组,牺牲空间换时间

3、关键代码:

void text(int a[],int len)//len表示数组个数

{

int b=a[0];//最大值

for(int i=1;i<len;i++)

         if(b<a[i])

                   b=a[i];

int d[b+1];//确保有b下标的存在

for(int i=0;i<=b;i++)///初始化

                   d[i]=0;

for(int i=0;i<len;i++)///开始计数

                 d[a[i]]+=1;

//回归原数组

for(int i=0,k=0;i<=b;i++)

         while(d[i]>0)

         {a[k]=i;k++;d[i]--;}    

}

(3.20桶排序

1、题干:

 

 

(3.21基数排序

1、题干

 

(3.22排序总结

1、冒泡排序:进行两次大循环,使小的元素不断往前冒(O(n^2)

2、选择排序:通过在整个数组中选择最小值与最前位置调换或同时找出最大值与最小值(O(n^2)

3、插入排序:通过循环让后一个元素插在前面合适的位置(O(n^2)

4、希尔排序:截取一半,将后面插入到前面去,再去一半的一半,继续插进去前面(nlgn

分治思想

5、快速排序:选一个主元划分成两个部分,把比主元小的放前面,大的放后面(nlgn)(O(n^2)

6、归并排序:创建辅助空间,把两组数据排好序(nlgn)需要辅助空间

7、堆排序:结合数据结构的二叉树,不断获取最大元素或最小元素与最后一个元素进行颠换(nlgn)基数大

以上都是通过比较进行的

8、计数排序:(kn)

9、桶排序:根据数的大小区间划分成几个桶,将数组根据其大小放进去,桶内进行插入排序(kn)

10、基数排序:固定开辟十个桶,根据数的级别决定操作次数k,又快又稳。(kn)

(3.23)节  排序数组中找和的因子

1、题干:

 

2、解答思路:根据条件说明是有序的数组,要求找两个和为指定数,首先求出该数的一半值在数组的位置,得出两个值在该位置的左右。

3、关键代码:

int text(int a[],int be,int end,int k)////求k的位置

{

int mid=be+((end-be)>>1);//中间下标

if(end-be<0)

return -1;//找不到返回-1

if(a[mid]==k)

return mid;//找到返回其下标

else if(a[mid]>k)

text(a,be,mid-1,k) ;

else

text(a,mid+1,end,k);

}

void  text2(int a[],int be ,int end,int k)

{int mid=text(a,be,end,k/2);//k/2的位置

////k的两个因数一个在mid的左边,一个在右边

for(int i=mid+1;i<=end;i++)

         {int L=k-a[i];////减去右边的值,得出的L值

         int T =text(a,be,mid,L);//找出L在数组的位置

                   if(T!=-1)

                   cout<<"("<<a[T]<<" "<<a[i]<<")";

         }

}

(3.24)节  需排序的最小子数组

1、题干

 

2、解答思路:找出转折点,在转折点中找出最大值与最小值,第一的转折点one的下标和最后一个转折点下标two,得出两个区间[be,one ],[two,end]再得出最大值最小值在区间内的该有位置,相减加1.

3、关键代码:

int text3(int a[],int end)//end表示数组a最后一个下标

{int d[end+1];//建立一个与a一样长的数组

for(int i=0;i<=end;i++)//d[]初始化为-1

d[i]=-1;

int len;//len为数组真实下标

///将转折点存进数组d 

for(int i=0,j=0;i<end;i++)

{        if( (i==0&&(a[i]>a[i+1])) ||( (a[i-1]<a[i] )&& (a[i]>a[i+1] ) ) )//中间最大

         {d[j]=i;len=j;j++;}//记录上顶点下标 ;偶

         if((i==end&&(a[i-1]>a[i]))||((a[i-1]>a[i])&&(a[i]<a[i-1])))//中间最小

         {d[j]=i;len=j;j++;}//记录下顶点;奇

////d数组里面是a的下标

}

int min=a[d[0]];

int max=a[d[0]];

for(int i=0;i<=len;i++)

{

         if(max<a[d[i]])

         max=a[d[i]];

         if(min>a[d[i]])

         min=a[d[i]];

}////最高顶点 与最低顶点的值

for(int i=0;i<=d[0];i++)

         if(a[i]>min)

                   {min=i;break;}

for(int i=end;i>=d[len];i--)

         if(a[i]<max)

                  {max=i;break;}

         cout<<endl;

         return max-min+1;

}

 

(3.25)节  前k个数

(3.26)节  堆处理

1、题干:

 

2、解答思路:当数组元素等于K时建立小顶堆,后面输入的元素与根节点作比较,大于根节点的话,就与根节点交换,  

(3.27)节 所有员工年龄排序

1、题干:

 

2、解答思路:要求效率非常高,且年龄的范围比较小所以可以使用计数排序法,创建一个100的数组,初始化为0,对输入的年龄进行计数,最后进行输出

3、关键代码:

Void mathnum()

{

         int age[100];

         for(int i=0;i<100;i++)

         age[i]=0;//初始化为0

         int a;

         cin>>a;

         while(a>0&&a<100)

         {

                   age[a]++;

                   cin>>a;

         }

         for(int i=0;i<=100;i++)

         while(age[i]>0)

         {

         cout<<i<<" ";

         age[i]--;

         }

}

(3.29)节  数组的包含

1、  题干:

 

2、  解题思路:对str2进行快速排序后,然后进行二分查找

3、  关键代码:

void text(char a[],int begin ,int end)//快排

{int mid =begin+((end-begin)>>1);

char b;

if((a[begin]>=a[mid]&&a[mid]>=a[end])||(a[begin]<=a[mid]&&a[mid]<=a[end]) )

{b=a[begin];a[begin]=a[mid];a[mid]=b;}

if((a[begin]<=a[end]&&a[end]<=a[mid])||(a[begin]>=a[end]&&a[end]>=a[mid]) )

{b=a[begin];a[begin]=a[end];a[end]=b;}

if(begin>=end)

return;

int L=begin,R=end;

while(L<=R)

{while(a[begin]>=a[L]&&L<=R)

L++;

 while(a[begin]<a[R]&&L<=R)

R--;

if(L<R)        {b=a[L];a[L]=a[R];a[R]=b;

}

}        

 b=a[begin];a[begin]=a[R];a[R]=b;

text(a,begin,R-1);

text(a,R+1,end);

}

int gotit(char a[],int begin,int end,char t)//二分查找

{

int mid=begin+((end-begin)>>1);

if(a[mid]==t)

return mid;

else if(a[mid]<t)

gotit(a,mid+1,end,t);

else

gotit(a,begin,mid-1,t);

return -1;

}

猜你喜欢

转载自www.cnblogs.com/niliuxiaocheng/p/11979244.html