算法 64式 14、排序算法整理_1_1到15题

1 算法思想

这里将寻找最小/大的前k个数,寻找逆序对,线性时间选择(寻找第k小/大的元素),奇偶/大小写字符分别放在前后部分等和排序相关类型的题目,放在了排序而不是查找中。

1.1含义

排序含义:重新排列列表中元素,使其递增或递减。

算法稳定性:若两个元素Ri,Rj对应值相等,经过排序后,Ri仍然在Rj前面,则说明该排序算法是稳定的。

1)插入排序

思想:将待排序元素插入到前面已经排好序的序列中

直接插入排序:

将元素L(i)插入到已经有序的子序列L[1…i-1]中,查找出L[i]在L[1…i-1]中的插入位置k,将L[k…i-1]中所有元素后移一个位置,令L[k]=L[i]

折半插入排序:

在直接插入排序的基础上,查找待插入位置时采用二分查找

希尔排序:

希尔排序是插入排序的变体,设置步长依次为n/2, n/4, ...,1,每次对步长上的元素进行插入排序。

 

2)选择排序

思想:第i趟在后面n-i+1个剩余未排序的所有元素中选择最小的元素作为序列中第i个元素。

简单选择排序:

第i趟排序从L[i…n]中选择关键字最小的元素与L[i]交换,每趟排序确定一个元素的最终位置

堆排序:

是树形选择排序。在排序过程中,将L[1..n]看成是完全二叉树的顺序存储结构,利用完全二叉树中父子之间关系,选择关键字最大/小的元素。

       大顶堆:任意父节点大于左右孩子节点,可用于生成递增的排序数组。可以解决求取最小的k个数的问题,因为堆顶是k个数中最大的,如果待比较元素小于堆顶元素,则可以用待比较元素替换大顶堆的堆顶元素。

       建堆:从n/2到1下标的元素,不断判断当前结点与左右孩子的值,若左右孩子值中的较大者大于父节点的值,则交换孩子结点与父节点

       调整: 每趟将堆顶元素与数组末尾元素交换,然后将除数组末尾的剩余元素部分再次调用建堆过程,使得剩余元素能成为大顶堆,重复上述过程,直到只有一个元素。

 

3)交换排序

思想:根据两个元素的大小来交换两个元素在序列中的位置

冒泡排序:从后向前两两比较相邻元素的值,若为逆序,则交换他们,一趟结束后,将最大元素放置数组末尾。

快速排序:基于分治,将待排序标L[1…n]中选取一个元素pivot作为枢轴,通过一趟排序将待排序标划分为L[1…k-1]和L[k+1,…n],

使得L[1…k-1]中所有元素小于pivot.L[k+1…n]中所有元素大于或等于pivot。

 

4)其他排序

归并排序:

将两个或两个以上的有序表组合成一个新的有序表。将n个记录的数组,看成n个有序的子表,两两归并,得到n/2个有序表,再两两归并,直到合并为一个长度为n的有序表为止。

基数排序:

第一趟选择个位,第二趟选择10位,...每一趟将数字上该位对应的值放入到对应编号的桶中,最后输出桶的结果即可。

 

1.2特点

1.3应用

快速排序的应用主要包括:查找第k大/小的元素

归并排序的应用主要包括: 逆序对等

堆排序的应用主要包括:小顶堆用于寻找前k个最大的元素,大顶堆用于寻找前k个最小的元素等。

 

1.4通用解法

排序算法

1 弄清楚各个排序算法的思想,重点关注:快速排序,堆排序和归并排序。

 

1.5经典例题讲解

快速排序

int randomInRange(int min,int max)

{

       return (rand()%(max - min + 1) + min);//牛逼,用除以这个范围内的数,加上min来得到min~max之间任意数

}

 

int partition(int *A,int low,int high)

{

       int iIndex = randomInRange(low,high);

       //int iPos = A[iIndex];

       swap(&A[low],&A[iIndex]);//交换第一个数与枢轴

       int iPos = A[low];

       while(low<high)//注意low<high,low=high会死循环

       {

              //后面>枢轴,则一直递减高位的指标

              //while(A[high]>iPos)//漏了low<high的判断条件,否则一直死循环

              while(low<high&&A[high]>= iPos)

              {

                     high--;

              }

              //后面<枢轴,这样确保A[low]一定是< iPos,用于下面while(low<high&&A[low]<= iPos)循环,确保该循环一开始可以跳过刚才交换后的元素

              A[low] = A[high];

              while(low<high&&A[low]<= iPos)

              {

                     low++;

              }

              A[high] = A[low];

       }

       A[low] = iPos;//易错,不能忘记对枢轴位置的元素进行正确赋值,随机选枢轴这步应该不需要了

       return low;//返回当前这一趟排序的枢轴位置

}

 

 

void quickSort(int *A,int low,int high)

{

       if(low<high)//注意low<high,low<=high,死循环

       {

              //printf("Enter quickSort\n");

              int iPos = partition(A,low,high);//0

              quickSort(A,low,iPos-1);//0,0

              quickSort(A,iPos+1,high);

       }

}

 

 

2 排序系列

类别-编号

题目

遁去的一

1

排序

对输入的n个数进行排序并输出

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47159077

用快速排序实现,参考经典例题解析

2

成绩排序
学生成绩排序:成绩从低到高,姓名字母从低到高(区分大小写),年龄从低到高排序

 

输入:

3

abc 20 99

bcd 19 97

bed 20 97

输出:

bcd 19 97

bed 20 97

abc 20 99

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47159193

易错点:

1对于字符指针,scanf("%s",ps[i].sName)不需要取地址符

2对于名字,要指定大小,否则内存访问非法,char sName[128];而不是定义char* sName;

3int strcmp(const char* string1,const char* string2); <0:前<后

 

代码:

typedef struct Student

{

       //char* sName;//就是这里出错了,没有具体制定多大

 

       //法二,用重载操作符operator<的方法做

       bool operator < (const Student& stu) const

       {

              if(iGrade != stu.iGrade)

              {

                     return iGrade < stu.iGrade;

              }

              else

              {

                     if(sName != stu.sName)

                     {

                            return strcmp(sName,stu.sName) < 0 ? true:false;

                     }

                     else

                     {

                            return iAge < stu.iAge;

                     }

              }

       }

       char sName[128];

       int iAge;

       int iGrade;

}Student;

 

int partition(Student* ps,int low,int high)

{

       Student stu = ps[low];

       while(low < high)

       {

              //while(low < high && compare(stu,ps[high]))//法1

              while(low < high && stu < ps[high])

              {

                     high--;

              }

              ps[low] = ps[high];

              //while(low < high && compare(ps[low],stu))//法1

              while(low < high && ps[low] < stu)

              {

                     low++;

              }

              ps[high] = ps[low];

       }

       ps[low] = stu;

       return low;

}

 

void quickSort(Student* ps,int low,int high)

{

       if(low < high)

       {

              int iPos = partition(ps,low,high);

              quickSort(ps,low,iPos-1);

              quickSort(ps,iPos+1,high);

       }

}

 

int main(int argc,char* argv[])

{

       int iNum;

       while(EOF!=scanf("%d",&iNum))

       {

              Student* ps = (Student*)malloc(iNum*sizeof(Student));

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

              {

                     //scanf("%s %d %d",&ps[i].sName,&ps[i].iAge,&ps[i].iGrade);    //关键字符串不需要取地址,因为它本身就是一个地址,否则就是取地址的地址了

                     scanf("%s %d %d",ps[i].sName,&ps[i].iAge,&ps[i].iGrade);

              }

              quickSort(ps,0,iNum-1);

              print(ps,iNum);

              free(ps);

       }

       return 0;

}

3

Sort

给n个整数,按从大到小的顺序,输出前m大的整数

 

输入:

5 3

3 -35 92 213 -644

输出:

213 92 3

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47159653

思路:

先按从小到大用快排排好序,然后输出排好序的数组从最后开始输出m个即可

关键:

1 已经达到千万数量级,1秒不能解决,必须用哈希,因为数字的范围达到百万级

2 哈希针对的是输入数值处于特定范围的问题,建立一个范围大小的数组,建立hash[x] = x出现多少次的映射

3 对于定义较大容量的数组,放在函数体外,这样用全局变量,内存会比较充足

4 对于区间为负的,需要设定下标补偿值

 

代码:

#define POS 500000

int iHash[1000001] = {0};

 

int main(int argc,char* argv[])

{

       int n,m,i;

       while(EOF!=scanf("%d%d",&n,&m) && m <= n && m > 0 && m < 1000000 && n > 0 && n < 1000000 )

       {

              for(i = 0; i < n ;i++)

              {

                     int iValue;

                     scanf("%d",&iValue);

                     if(iValue < -500000 || iValue > 500000)

                     {

                            return 0;

                     }

                     else

                     {

                            iHash[iValue + POS] = 1;//下标和值存在某种一元函数关系

                     }

              }

              for(i = 500000 ; i >= -500000 ; i--)

              {

                     if(iHash[i + POS]==1)

                     {

                            m--;

                            printf("%d ",i);

                     }

                     //if(m)//应该是m=0打印空格

                     if(0==m)

                     {

                            printf("\n");

                            break;

                     }

              }

              //printf("\n");

       }

       system("pause");

       getchar();

       return 0;

}

4

最小的k个数

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

 

输入:

每个测试案例包括2行:

第一行为2个整数n,k(1<=n,k<=200000),表示数组的长度。

第二行包含n个整数,表示这n个数,数组中的数的范围是[0,1000 000 000]。

输出:

对应每个测试案例,输出最小的k个数,并按从小到大顺序打印。

样例输入:

8 4

4 5 1 6 2 7 3 8

10 5

1 9 8 6 4 1 0 4 3 5

样例输出:

1 2 3 4

0 1 1 3 4

剑指offer

https://blog.csdn.net/qingyuanluofeng/article/details/39138417

关键:

1 利用划分函数,对下标为k的数字进行划分,那么最左边的k各数字一定是最小的k个

数字,但是不一定有序。

2 while(iIndex != k-1 )//注意,这里不是iIndex = k,因为数组的第k-1下标,表示数组中第k个数

3 O(nlogK)的算法,适合处理海量数据(适合n很大,k很小)

先创建一个大小为k的数据容器来存储最小的k个数字,每次从输入的n个整数中读入一个数。如果容器中已有的数字

少于k个,直接把这次读入的整数放入容器中。如果容器中已经有k个数,此时只能替换原有数字。找出k个数字中

的最大值,然后依次拿待插入的整数和最大值进行比较,若待插入的小,就替换。

4 vector<int>::const_iterator itV;//对于常量容器,必须要用常量迭代器

5 if(setLeastK.size() < k)//如果存储最小k个元素的容器大小<K,则直接加入

{

setLeastK.insert(*itV);

}

else//如果已经达到k个元素,则找出其中的最大元素,删除该最大元素后,再加入新的小元素

6int low = 0,high = iLen -1;//注意,这里做划分的函数的下标必须能取到

7 greater表示内置类型从大到小排序,less表示内置类型从小到大排序。所有greater得到的是大顶堆

 

代码:

const int MAXSIZE = 200001;

int iArr[MAXSIZE];

 

int randomInRange(int min,int max)

{

       return (rand() % (max - min + 1) + min);

}

 

void swap(int* pNum1,int* pNum2)

{

int iTemp = *pNum1;

*pNum1 = *pNum2;

*pNum2 = iTemp;

}

 

int partition(int low,int high)

{

       int iIndex = randomInRange(low,high);

       swap(&iArr[low],&iArr[iIndex]);

       int iAxis = iArr[low];

       while(low < high)

       {

              while(low < high && iArr[high] >= iAxis)

              {

                     high--;

              }

              iArr[low] = iArr[high];

              while(low < high && iArr[low] <= iAxis)

              {

                     low++;

              }

              iArr[high] = iArr[low];

       }

       iArr[low] = iAxis;

       return low;

}

 

void minKNum(int iLen,int k)

{

       int low = 0,high = iLen -1;//注意,这里做划分的函数的下标必须能取到

       int iIndex = partition(low,high);

       while(iIndex != k-1 )//注意,这里不是iIndex = k,因为数组的第k-1下标,表示数组中第k个数

       {

              if(iIndex > k-1 )

              {

                     high = iIndex - 1;

              }

              else

              {

                     low = iIndex + 1;

              }

              iIndex = partition(low,high);

       }

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

       {

              if(i)

              {

                     printf(" %d",iArr[i]);

              }

              else

              {

                     printf("%d",iArr[i]);

              }

       }

       printf("\n");

}

 

//用vector来存储数据,用multiset来存储最小的k个数,注意这里用greater<int>来确保容器中的第一个数字是最大的数字

// greater表示内置类型从大到小排序,less表

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值