题1:8638 直接插入排序
题目描述
Description
用函数实现直接插入排序,并输出每趟排序的结果.
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出一趟排序结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
4 5 8 0 9 3 2 6 7 1
4 5 8 0 9 3 2 6 7 1
0 4 5 8 9 3 2 6 7 1
0 4 5 8 9 3 2 6 7 1
0 3 4 5 8 9 2 6 7 1
0 2 3 4 5 8 9 6 7 1
0 2 3 4 5 6 8 9 7 1
0 2 3 4 5 6 7 8 9 1
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
每步都将一个待排序的对象,按其关键码的大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入到正确的位置。
也就是边插入边排序。
具体过程:起初,a[0]是长度为1的子序列,然后逐一将a[1]到a[n-1]插入到有序子序列中。
在插入a[i]之前, 数组a的前半段(a[0]-a[i-1])是有序段,而后半段仅仅只是含有输入次序关系的无序段
插入a[i]使得a[0]-a[i]这一段都要是有序的,也就是要为a[i]找到有序位置j,那么j的范围也就是有序段中的任意一个位置,也就是在(0-i),注意,也有可能在最后插入的,所以要搞清楚遍历元素的范围。
插入位置的确定:首先要找到,他在有序段中,他比前一个的要大,他比后一个的要小,这个位置就是他的位置,假如从头找到尾都没有找到比他小的数,也就是j到i了,这时候直接插入到尾部即可。
所以一共分为三种情况:(1)插入到中间,这是最普遍的情况(2)插入到最前面。(3)插入到最后面。
插入排序的种类有三种:顺序法定位插入位置:这种排序方法就是直接插入排序。
二分法:二分插入排序
缩小增量,多遍插入排序:希尔排序
那么做插入排序的基本步骤就是:定位插入位置,将需要搬动元素整体向后移动。直到所有元素都定位到正确的位置为止。
AC代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int *a,n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void InsertSort()
{
int i,j;
for(i=2;i<=n;i++)
{
a[0]=a[i];
for(j=i-1;a[0]<a[j];j--)//如果说当前遍历的元素比哨兵元素仍然要大的时候就要向前走
{
a[j+1]=a[j];
}
a[j+1]=a[0];
Print();
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
a=new int[n+5];
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
InsertSort();
}
题2:8639 折半插入排序
题目描述
Description
用函数实现折半插入排序,并输出每趟排序的结果.
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出一趟排序结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
4 5 8 0 9 3 2 6 7 1
4 5 8 0 9 3 2 6 7 1
0 4 5 8 9 3 2 6 7 1
0 4 5 8 9 3 2 6 7 1
0 3 4 5 8 9 2 6 7 1
0 2 3 4 5 8 9 6 7 1
0 2 3 4 5 6 8 9 7 1
0 2 3 4 5 6 7 8 9 1
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
排序的基本思路仍然是插入排序,但是可以通过折半二分的方式来提高找到插入的概率和效率。
那么是什么原理呢?在上面已经说到,正确的插入位置应当是左边比他小,右边比他大,那么在这个查找的过程中,是在有序段查找的,这正好符合我们二分查找的思路,所以就可以在查找的这个过程中加入二分的算法,来提高我们的算法效率。
AC代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int *a,n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void InsertSort()
{
int i,j;
for(i=2;i<=n;i++)
{
a[0]=a[i];
/*在这里改动*/
int l=1,r=i-1,mid=(l+r)/2;/*int运算,自动向下取整,不担心*/
while(l<=r)
{
mid=(l+r)/2;//要找的是比a[0]小的元素
if(a[0]<a[mid])
{
r=mid-1;/*假如在有序区,待插入的元素比中间元素小,那么就可以锁定a[0]应当放在左边那个区间*/
}
else
{
l=mid+1;/*假如在有序区,待插入的元素比中间元素大,那么就可以锁定a[0]应当放在右边那个区间*/
}
}//当就是说,待插入的元素已经实现一个比左边大,比右边小的时候,就会不断触发这两个条件中的其中一个,最终导致r<l,就跳出循环了,具体的过程可以画个图看一下,最后插入位置应当是r+1
//找到插入位置后进入搬动元素
for(j=i-1;j>=r+1;j--)
{
a[j+1]=a[j];
}
a[j+1]=a[0];
Print();
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
a=new int[n+5];
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
InsertSort();
}
题3:8640 希尔(shell)排序
题目描述
Description
用函数实现希尔(shell)排序,并输出每趟排序的结果,初始增量d=n/2,其后d=d/2
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出一趟排序结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
3 2 6 0 1 5 4 8 7 9
1 0 3 2 4 5 6 8 7 9
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
缩小增量,多遍插入排序:希尔排序。
原本的直接插入排序就是比较一次,指针只移动一位,而希尔排序的话就是比较一次,指针移动若干位,这个位,我们称为增量。
具体步骤:先将整个待排序记录序列分割位若干个子序列,分别进行直接插入排序,待整个序列中的记录”基本有序“时,再对全体记录进行一次直接插入排序。注意:增量是会逐渐递减的,直到最后做成一个1的直接插入排序,注意,增量序列应当是互质的.
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e5+5;
int a[M],n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void InsertSort()
{
int t,k,i;
for(t=n/2;t>0;t/=2)
{
for(i=t;i<=n;i++)
{
a[0]=a[i];
for(k=i-t; k>=0 && a[k]>a[0];k-=t)
{
a[k+t]=a[k];
}
a[k+t]=a[0];
}
Print();
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
InsertSort();
return 0;
}
题4:8641 冒泡排序
题目描述
Description
用函数实现冒泡排序,并输出每趟排序的结果(要求当一趟冒泡过程中不再有数据交换,则排序结束)
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出每趟排序结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
4 5 0 8 3 2 6 7 1 9
4 0 5 3 2 6 7 1 8 9
0 4 3 2 5 6 1 7 8 9
0 3 2 4 5 1 6 7 8 9
0 2 3 4 1 5 6 7 8 9
0 2 3 1 4 5 6 7 8 9
0 2 1 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
冒泡排序属于交换排序的范畴,也就是遍历数组,当遇到逆序的时候就完成一次交换,直到遍历数组时没有再存在逆序的情况为止。
那么冒泡排序的基本思想就是:每趟不断将记录两两比较,并按照前小后大的给规则交换。
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e5+5;
int a[M],n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void SwapSort()
{
int i,j;
for(i=1;i<=n-1;i++)
{
bool t=false;
for(j=1;j<n;j++)
{
if(a[j]>a[j+1])
{
t=true;
swap(a[j],a[j+1]);
}
}
Print();
if(!t)
{
break;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
SwapSort();
return 0;
}
题5:8642 快速排序
题目描述
Description
用函数实现快速排序,并输出每次分区后排序的结果
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出每趟排序的结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
1 4 2 0 3 5 9 6 7 8
0 1 2 4 3 5 9 6 7 8
0 1 2 4 3 5 9 6 7 8
0 1 2 3 4 5 9 6 7 8
0 1 2 3 4 5 8 6 7 9
0 1 2 3 4 5 7 6 8 9
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
任取一个元素(如第一个)为中心
所有比他小的元素一律向前放,比他大的元素一律向后放,从而形成左右两个子表,然后再对各子表进行重选中心元素,然后依此规则进行调整,算法的出口是当子表中只剩下一个元素了(因为一个元素总是有序的)
通过一趟排序,将待排序的记录分割成独立的两部分,其分割出来的两个部分记录的关键字中,左边的均比右边的要小,然后通过分治的方法来进行排序,从而达到排序的效果。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e5+5;
int a[M],n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
int pos(int l,int r)
{
a[0]=a[l];
while(l<r)
{
while(l<r && a[r]>=a[0])
{
r--;
}
a[l]=a[r];
while(l<r && a[l]<=a[0])
{
l++;
}
a[r]=a[l];
}
a[l]=a[0];
Print();
return l;
}
void Sort(int l,int r)
{
if(l<r)
{
int mid=pos(l,r);
Sort(l,mid-1);
Sort(mid+1,r);
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
Sort(1,n);
return 0;
}
题6:8643 简单选择排序
题目描述
Description
用函数实现简单选择排序,并输出每趟排序的结果
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出每趟排序的结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
0 4 8 5 9 3 2 6 7 1
0 1 8 5 9 3 2 6 7 4
0 1 2 5 9 3 8 6 7 4
0 1 2 3 9 5 8 6 7 4
0 1 2 3 4 5 8 6 7 9
0 1 2 3 4 5 8 6 7 9
0 1 2 3 4 5 6 8 7 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
在待排序的数据中选出最大(或者最小)的元素放在其最终的位置,这个排序算法与冒泡排序很像,但是是有目的性的,冒泡排序是1-n随机元素放在属于自己的位置,而简单插入排序则是按照关键字的大小来确定谁先进行排序,谁后进行排序。
AC代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int M=1e5+5;
int a[M],n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void SwapSort()
{
int i,j,t;
for(i=1;i<n;i++)
{
t=i;
for(j=i+1;j<=n;j++)
{
if(a[j]<a[t])
{
t=j;
}
}
swap(a[i],a[t]);
Print();
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
SwapSort();
return 0;
}
题7:8644 堆排序
题目描述
Description
用函数实现堆排序,并输出每趟排序的结果
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
第一行:初始建堆后的结果
其后各行输出交换堆顶元素并调整堆的结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
9 7 8 6 4 3 2 5 0 1
8 7 3 6 4 1 2 5 0 9
7 6 3 5 4 1 2 0 8 9
6 5 3 0 4 1 2 7 8 9
5 4 3 0 2 1 6 7 8 9
4 2 3 0 1 5 6 7 8 9
3 2 1 0 4 5 6 7 8 9
2 0 1 3 4 5 6 7 8 9
1 0 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
堆的定义,若n个元素的序列a1,a2…an都满足有
ai<=a2i && ai<=a2i+1的话,这个堆称为小根堆
ai>=a2i && a>=a2i+1的话,这个堆称为大根堆
那么从数据结构的角度上来看,堆实际上就是满足以下性质的完全二叉树:二叉树中任一非叶子节点均小于(大于)它的孩子节点
那么在这两种堆中,根据以上的性质,就可以知道,大根堆的堆顶,就是最大值,小根堆的堆顶,就是最小值。
那么在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次最小值,或者得到次最大值,如此反复,就能够得到一个有序序列,那么这个过程就称为堆排序。
需要解决两个问题:
①如何由一个无序序列建成一个堆?
②如何在输出堆顶元素后,把剩下的元素调整为一个新的堆?
对于小根堆而言:
①输出堆顶元素之后,以堆中最后一个元素替代之
②然后将根节点值与左、右子数的根节点值进行比较,并与其中小者进行交换
③重复上述操作,直至叶子节点,将得到新的堆。
关键步骤:取原堆中的最后一个元素,将其置于堆顶,比较其左右孩子的大小,并与其中较小值交换。
重要公式,计算一个数组下标所对应的父节点和左右子节点
假设当前需要heapify的数组节点下标是i,那么他的父节点的下标就是(i-1)/2,则其左右子节点的下标分别是2i+1,2i+2;
注意int除法的自动向下取整的功能,所以不需要担心
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e5+5;
int a[M],n;
void Print()
{
for(int i=0;i<n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void Heapift(int idx,int cnt)
{
if(idx>=cnt)
{
return ;
}
int c1=2*idx+1;
int c2=2*idx+2;
int Max=idx;
if(c1<cnt && a[Max]<a[c1])
{
Max=c1;
}
if(c2<cnt && a[Max]<a[c2])
{
Max=c2;
}
if(Max!=idx)
{
swap(a[idx],a[Max]);
Heapift(Max,cnt);
}
}
void BuiltHeap()
{
int leaf=n-1;
int p=(leaf-1)/2;
for(int i=p;i>=0;i--)
{
Heapift(i,n);
}
Print();
}
void HeapSort()
{
BuiltHeap();
int i;
for(i=n-1;i>0;i--)
{
swap(a[i],a[0]);
Heapift(0,i);
Print();
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
HeapSort();
return 0;
}
题8:8645 归并排序(非递归算法)
题目描述
Description
用函数实现归并排序(非递归算法),并输出每趟排序的结果
输入格式
第一行:键盘输入待排序关键的个数n
第二行:输入n个待排序关键字,用空格分隔数据
输出格式
每行输出每趟排序的结果,数据之间用一个空格分隔
输入样例
10
5 4 8 0 9 3 2 6 7 1
输出样例
4 5 0 8 3 9 2 6 1 7
0 4 5 8 2 3 6 9 1 7
0 2 3 4 5 6 8 9 1 7
0 1 2 3 4 5 6 7 8 9
提示
作者 yqm
排序基本思想
归并排序的操作是建立在归并操作上的一种有效的算法,归并排序对序列元素进行逐层折半分组,然后从最小分组开始比较,合并成一个大的数组,逐层进行,最终所有的元素都是有序的。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e5+5;
int a[M],n;
void Print()
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void Merge(int num[],int l,int mid,int r)
{
int *tmp=new int[r-l];
int i=l,j=mid,k=0;
while(i<mid && j<r)
{
if(num[i]>num[j])
{
tmp[k++]=num[j++];
continue;
}
tmp[k++]=num[i++];
}
while(i<mid)
{
tmp[k++]=num[i++];
}
while(j<r)
{
tmp[k++]=num[j++];
}
for(int i=0;i<k;i++)
{
num[l++]=tmp[i];
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int wid=1;
for(wid=1;wid<n;wid*=2)
{
int bg=1,ed,mid;
for(bg=1;bg<=n;bg+=wid*2)
{
mid=bg+wid;
ed=bg+2*wid;
if(mid>n)
{
mid=n+1;
}
if(ed>n)
{
ed=n+1;
}
Merge(a,bg,mid,ed);
}
Print();
}
}
题1:8647 实现图的存储结构
题目描述
Description
实现有向图的邻接矩阵存储结构。
输入格式
第一行:输入图的顶点个数n(各个顶点的默认编号为1~n), 边的条数m。
第二 ~ m+1行:每行输入两个顶点编号i、j,表示连接顶点i到顶点j的一条边。
输出格式
分n行输出n*n的邻接矩阵,表示所输入的图存储,顶点i和顶点j之间如果有边相连,则输出1,没边相连则输出0。
输入样例
4 4
1 2
1 3
3 4
4 1
输出样例
0 1 1 0
0 0 0 0
0 0 0 1
1 0 0 0
提示
作者 yqm
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e3+5;
int a[M][M];
int main()
{
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++)
{
int x,y;cin>>x>>y;
a[x][y]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<a[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
题2:8648 图的深度遍历
题目描述
Description 实现图的邻接表存储结构及一些基本操作函数。在此基础上实现图的深度遍历算法并加以测试。本题只给出部分代码,请补全内容。输入格式
第一行:输入0到3之间整数(有向图:0,有向网:1,无向图:2,无向网:3);
第二行:输入顶点数和边数;
第三行:输入各个顶点的值(字符型,长度〈3);(遍历从输入的第一个顶点开始)
第四行:输入每条弧(边)弧尾和弧头(以空格作为间隔),如果是网还要输入权值;
输出格式
输出对图深度遍历的结果。
输入样例
0
3 3
a b c
a b
b c
c b
输出样例
a b c
提示
注意题目的邻接表采用的是头插法,也就是后出现的边节点先被访问。
作者 yqm
AC代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int M=1e5+5;
vector <int> e[M];
int book[M];
void dfs(int cur)
{
cout<<(char)(cur+'a')<<' ';
int len=e[cur].size();
for(int i=0;i<len;i++)
{
int y=e[cur][i];
if(book[y]==0)
{
book[y]=1;
dfs(y);
}
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
int t;cin>>t;
int n,m;cin>>n>>m;
int st;
for(int i=0;i<n;i++)
{
char x;cin>>x;/*cjb*/
}
for(int i=0;i<m;i++)
{
char x,y;cin>>x>>y;
int a=(char)(x-'a');
int b=(char)(y-'a');
if(i==0)
{
st=a;
}
if(t<=1)
{
e[a].insert(e[a].begin(),b);
}
else
{
e[a].insert(e[a].begin(),b);
e[b].insert(e[b].begin(),a);
}
}
book[st]=1;
dfs(st);
return 0;
}
题3:8649 图的广度遍历
题目描述
Description
使用图的深度遍历实现的邻接表存储结构和基本操作函数,在此基础上实现图的广度遍历算法并加以测试。注意正确使用队列存储结构。
输入格式
第一行:输入0到3之间整数(有向图:0,有向网:1,无向图:2,无向网:3);
第二行:输入顶点数和边数;
第三行:输入各个顶点的值(字符型,长度〈3);(遍历从输入的第一个顶点开始)
第四行:输入每条弧(边)弧尾和弧头(以空格作为间隔),如果是网还要输入权值;
输出格式
输出对图广度遍历的结果
输入样例
0
3 3
a b c
a b
b c
c b
输出样例
a b c
提示
注意题目的邻接表采用头插法,也就是后出现的边节点插入到邻接表的表头。
作者 yqm
AC代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int M=1e5+5;
vector <int> e[M];
int q[M];
int book[M];
int main()
{
ios::sync_with_stdio(false);
int t;cin>>t;
int n,m;cin>>n>>m;
int st;
for(int i=0;i<n;i++)
{
char x;cin>>x;/*cjb*/
}
for(int i=0;i<m;i++)
{
char x,y;cin>>x>>y;
int a=(char)(x-'a');
int b=(char)(y-'a');
if(i==0)
{
st=a;
}
if(t<=1)
{
e[a].insert(e[a].begin(),b);
}
else
{
e[a].insert(e[a].begin(),b);
e[b].insert(e[b].begin(),a);
}
}
int head=0,tail=0;
book[st]=1;
q[tail++]=st;
while(head<tail)
{
int y=q[head];
head++;
int len=e[y].size();
cout<<(char)(y+'a')<<' ';
for(int i=0;i<len;i++)
{
if(book[e[y][i]]==0)
{
book[e[y][i]]=1;
q[tail++]=e[y][i];
}
}
}
return 0;
}