数据结构实验3-4——2020
问题 A: 子网掩码
题目描述
子网掩码是用来判断任意两台计算机的IP地址是否属于同一子网络的根据。
最为简单的理解就是两台计算机各自的IP地址与子网掩码进行AND运算后,如果得出的结果是相同的,则说明这两台计算机是处于同一个子网络上的,可以进行直接的通讯。就这么简单。
请看以下示例:
运算演示之一:
IP地址 192.168.0.1
子网掩码 255.255.255.0
转化为二进制进行运算:
IP地址 11010000.10101000.00000000.00000001
子网掩码 11111111.11111111.11111111.00000000
AND运算:
11010000.10101000.00000000.00000000
转化为十进制后为:
192.168.0.0
运算演示之二:
IP地址 192.168.0.254
子网掩码 255.255.255.0
转化为二进制进行运算:
IP地址 11010000.10101000.00000000.11111110
子网掩码 11111111.11111111.11111111.00000000
AND运算:
11010000.10101000.00000000.00000000
转化为十进制后为:
192.168.0.0
运算演示之三:
IP地址 192.168.0.4
子网掩码 255.255.255.0
转化为二进制进行运算:
IP地址 11010000.10101000.00000000.00000100
子网掩码 11111111.11111111.11111111.00000000
AND运算:
11010000.10101000.00000000.00000000
转化为十进制后为:
192.168.0.0
通过以上对三组计算机IP地址与子网掩码的AND运算后,我们可以看到它运算结果是一样的,均为192.168.0.0,所以计算机就会把这三台计算机视为在同一子网络。
输入
输入的第一行是本机IP地址;
第二行是子网掩码;
第三行是一个整数N,表示后面有N个IP地址;
接下来N行:
第1个IP地址
...
...
第N个IP地址
输出
计算并输出N个IP地址是否与本机在同一子网内。对于在同一子网的输出“INNER”,对于在不同子网的输出“OUTER”。
样例输入
192.168.0.1
255.255.255.0
3
192.168.0.2
192.168.0.254
192.168.1.2
样例输出
INNER
INNER
OUTER
代码
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
string dot2space(string a)
{
for (int i = 0; i < a.length(); i++)
{
if (a[i] == '.')
{
a[i] = ' ';
}
}
return a;
}
string clca(string ip, string sub)
{
int ip2[4], sub2[4];
string addr = "";
ip = dot2space(ip);
sub = dot2space(sub);
stringstream s1(ip);
stringstream s2(sub);
for (int i = 0; i < 4; i++)
{
s1 >> ip2[i];
s2 >> sub2[i];
addr += to_string(ip2[i] & sub2[i]);
}
return addr;
}
int main()
{
string localIP, sub, addr;
int n;
cin >> localIP >> sub;
cin >> n;
addr = clca(localIP, sub);
string *ip = new string[n];
for (int i = 0; i < n; i++)
{
cin >> ip[i];
}
for (int i = 0; i < n; i++)
{
if (clca(ip[i], sub) == addr)
cout << "INNER" << endl;
else
cout << "OUTER" << endl;
}
return 0;
}
问题 B: 快来秒杀我
题目描述
根据前几次竞赛的情况,这次为了给新手们一点信心,特提供这道秒杀题来让大家杀。
ASCII码大家应该都学过了,现在给你一个很简单的任务,输入数字,表示ASCII码,输出对应的文本内容。
输入
输入的第一行是一个整数T(1<=T<=100)。
接下来输入T个正整数,这些数之间用空格、换行或Tab键来分隔。
测试数据保证输入的整数都在ASCII码范围内,并且不小于32。
输出
在一行中输出对应的文本内容。
样例输入
13
72 101 108 108 111 44
32 119 111 114 108 100 33
样例输出
Hello, world!
代码
#include<iostream>
using namespace std;
int main()
{
int t;
cin>>t;
int a[t];
for(int i=0;i<t;i++)
cin>>a[i];
for(int j=0;j<t;j++)
cout<<static_cast<char>(a[j]);
}
问题 C: 算法:迪杰斯特拉最短路径算法
题目描述
在带权有向图G中,给定一个源点v,求从v到G中的其余各顶点的最短路径问题,叫做单源点的最短路径问题。
在常用的单源点最短路径算法中,迪杰斯特拉算法是最为常用的一种,是一种按照路径长度递增的次序产生最短路径的算法。
可将迪杰斯特拉算法描述如下:
在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并按照以上描述中的算法求出源点至每一个其它顶点的最短路径长度。
输入
输入的第一行包含2个正整数n和s,表示图中共有n个顶点,且源点为s。其中n不超过50,s小于n。
以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出
只有一行,共有n-1个整数,表示源点至其它每一个顶点的最短路径长度。如果不存在从源点至相应顶点的路径,输出-1。
请注意行尾输出换行。
样例输入
4 1
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
样例输出
6 4 7
提示
在本题中,需要按照题目描述中的算法完成迪杰斯特拉算法,并在计算最短路径的过程中将每个顶点是否可达记录下来,直到求出每个可达顶点的最短路径之后,算法才能够结束。
迪杰斯特拉算法的特点是按照路径长度递增的顺序,依次添加下一条长度最短的边,从而不断构造出相应顶点的最短路径。
另外需要注意的是,在本题中为了更方便的表示顶点间的不可达状态,可以使用一个十分大的值作为标记。
代码
#include<iostream>
using namespace std;
#define INF 999999
int e[55][55]; //邻接矩阵
int dis[55]; //源点至各个结点的最短路径
int p[55] = {0}; //标志
int s,n;
int main()
{
cin >> n >> s; //顶点个数与源点
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
cin >> e[i][j];
if (e[i][j] == 0&&i!=j)e[i][j] = INF; //输入邻接矩阵,对角线为0,不可达为INF
if (i == s)dis[j] = e[i][j]; //初始化dis
}
}
for (int i = 0; i < n - 1; i++) //n-1次即可遍历完
{
int min = INF;
int u;
for (int j = 0; j < n; j++) //寻找距离源点最近的点
{
if (j != s&&p[j] == 0 && dis[j] < min)
{
min = dis[j];
u = j;
}
}
p[u] = 1; //选中,记录
for (int v = 0; v < n; v++) //对u的所有出边进行松弛(即以u为中间节点)
{
if (e[u][v] < INF)
{
if (dis[v] > dis[u] + e[u][v]) //u为中间结点
{
dis[v] = dis[u] + e[u][v];
}
}
}
}
for (int i = 0; i < n; i++)
{
if (i != s)
{
if (dis[i] == INF)cout << -1<<" ";
else cout << dis[i] << " ";
}
}
cout << endl;
return 0;
}
问题 D: 二叉排序树
题目描述
输入一系列整数,建立二叉排序数,并进行前序,中序,后序遍历。
输入
输入第一行包括一个整数n(1<=n<=100)。接下来的一行包括n个整数。
输出
可能有多组测试数据,对于每组数据,将题目所给数据建立一个二叉排序树,并对二叉排序树进行前序、中序和后序遍历。每种遍历结果输出一行。每行最后一个数据之后有一个空格。
样例输入
12 28 15 421 10 5 39
样例输出
2 2 2 8 15 8 15 15 8 21 10 5 39 5 10 21 39 5 10 39 21
代码
#include <iostream>
using namespace std;
struct node
{
int data;
node *lchild;
node *rchild;
node(int num) : data(num), lchild(NULL), rchild(NULL) {}
};
int insert(int num, node *&T) //*&是关键
{
if (!T)
{
T = new node(num);
}
if (num < T->data)
{
insert(num, T->lchild);
}
if (num > T->data)
{
insert(num, T->rchild);
}
return 0;
}
void preOrder(node *T)
{
if (!T)
{
return;
}
cout << T->data << " ";
preOrder(T->lchild);
preOrder(T->rchild);
}
void inOrder(node *T)
{
if (!T)
{
return;
}
inOrder(T->lchild);
cout << T->data << " ";
inOrder(T->rchild);
}
void postOrder(node *T)
{
if (!T)
{
return;
}
postOrder(T->lchild);
postOrder(T->rchild);
cout << T->data << " ";
}
int main()
{
int n;
node *root;
while (cin >> n)
{
int num;
root = NULL;
for (int i = 0; i < n; i++)
{
cin >> num;
//以插入的方式建立二叉树
insert(num, root);
}
preOrder(root);
cout << endl;
inOrder(root);
cout << endl;
postOrder(root);
cout << endl;
}
return 0;
}
问题 E: 密码锁
题目描述
玛雅人有一种密码,如果字符串中出现连续的2012四个数字就能解开密码。给一个长度为N的字符串,(2=<N<=13)该字符串中只含有0,1,2三种数字,问这个字符串要移位几次才能解开密码,每次只能移动相邻的两个数字。例如02120经过一次移位,可以得到20120,01220,02210,02102,其中20120符合要求,因此输出为1.如果无论移位多少次都解不开密码,输出-1。
输入
第一行输入N,第二行输入N个数字,只包含0,1,2
输出
样例输入
502120502120
样例输出
11
代码
#include <iostream>
#include <string>
#include <queue>
#include <map>//bfs
using namespace std;
map<string, int> M;
queue<string> Q;
//交换str的第i位和第i+1位
string Swap(string str, int i)
{
string newStr = str;
char tmp = newStr[i];
newStr[i] = newStr[i + 1];
newStr[i + 1] = tmp;
return newStr;
}
//判断是否满足条件
bool Judge(string str)
{
if (str.find("2012", 0) == string::npos)
return false;
else
return true;
}
//BFS
int BFS(string str)
{
string newStr;
M.clear();
while (!Q.empty())
Q.pop();
//初始str入列
Q.push(str);
//字符串为键,交换次数为值,初始移动次数为0
M[str] = 0;
//只要队列中还有组合的字符串
while (!Q.empty())
{
//队首的字符串出列
str = Q.front();
Q.pop();
//遍历该字符串所有交换2位的组合
for (int i = 0; i <= str.length() - 1; i++)
{
newStr = Swap(str, i);
//若交换后的新字符串未出现过
if (M.find(newStr) == M.end())
{
//此新字符串的交换次数等于上一轮循环字符串的交换次数加1
M[newStr] = M[str] + 1;
//判断该字符是否符合要求,符合则返回交换次数,不符合则将该字符串入列,进行下一轮遍历
if (Judge(newStr))
return M[newStr];
else
Q.push(newStr);
}
//如果该新字符串已出现过,则直接跳过
else
continue;
}
}
return -1;
}
int main()
{
int n;
string str;
while (cin >> n)
{
cin >> str;
if (Judge(str) == true)
cout << "0" << endl;
else
{
int ans = BFS(str);
cout << ans << endl;
}
}
return 0;
}
#include<iostream>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
struct MoveStr{
string str;
int step;
};
int size;//记录string在map集合中的位置,每加入一个新的字符串size加1
queue<MoveStr> Q;
map<string,int> M;
/*查找所给字符串是否在集合中,若不在集合中,
则将新的结构体变量压入队列*/
bool check(string str){//检查字符串是否满足2012的条件
int len=str.length();
for(int i=0;i<len-3;i++){
if(str[i]=='2' && str[i+1]=='0' &&
str[i+2]=='1' && str[i+3]=='2'){
return true;
}
}
return false;
}
int BFS(){
while(Q.empty()==false){
MoveStr head=Q.front();//取出队首元素
Q.pop();
if(check(head.str)){//若字符串满足2012的条件,返回移位操作的最小次数
return head.step;
}
for(int i=0;i<head.str.length()-1;i++){
string strtmp=head.str;
//移位一次,交换两个相邻字符
char chartmp=strtmp[i];
strtmp[i]=strtmp[i+1];
strtmp[i+1]=chartmp;
if(M.find(strtmp)==M.end()){
/*检查移位一次之后的字符串strtmp是否在map集合中,
若不在,find()函数返回M.end(),则将(strcmp,step+1)入队
*/
MoveStr tmp;
tmp.str=strtmp;
tmp.step=head.step+1;//移位次数+1
Q.push(tmp);
M[strtmp]=size++;
}
}
}
return -1;
}
int main(){
int n;
while(cin>>n){
while(Q.empty()==false) Q.pop();//初始化队列,清空队列
M.clear();//清空一个map
size=0;
string str;
cin>>str;
M[str]=size++;
MoveStr s;
s.str=str;
s.step=0;
Q.push(s);
int result=BFS();
cout<<result<<endl;
}
return 0;
}
问题 F: 算法:快速排序
题目描述
快速排序是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排序的记录分割成两个独立的部分,其中一部分记录的关键字均比另一部分的关键字小,在分成两个部分之后则可以分别对这两个部分继续进行排序,从而使整个序列有序。
快速排序的算法可以描述如下:
在本题中,读入一串整数,将其使用以上描述的快速排序的方法从小到大排序,并输出。
输入
输入的第一行包含1个正整数n,表示共有n个整数需要参与排序。其中n不超过100000。
第二行包含n个用空格隔开的正整数,表示n个需要排序的整数。
输出
只有1行,包含n个整数,表示从小到大排序完毕的所有整数。
请在每个整数后输出一个空格,并请注意行尾输出换行。
样例输入
102 8 4 6 1 10 7 3 5 9
样例输出
1 2 3 4 5 6 7 8 9 10
提示
在本题中,需要按照题目描述中的算法完成快速排序的算法。
快速排序是一种十分常用的排序算法,其平均时间复杂度为O(knlnn),其中n为待排序序列中记录的个数,k为常数。大量的实际应用证明,在所有同数量级的此类排序算法中,快速排序的常数因子k是最小的,因此,就平均时间而言,快速排序是目前被认为最好的一种内部排序方法。
而在C语言的常用编译器中,qsort函数是一个非常常用的快速排序函数。
代码
//偷懒版
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int n;
cin>>n;
int a[n];
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
for(int i=0;i<n;i++)
cout<<a[i]<<' ';
cout<<endl;
}
//另一
#include <iostream>
using namespace std;
void Qsort(int arr[], int low, int high)
{
if (high <= low) return;
int i = low;
int j = high + 1;
int key = arr[low];
while (true)
{
/*从左向右找比key大的值*/
while (arr[++i] < key)
{
if (i == high){
break;
}
}
/*从右向左找比key小的值*/
while (arr[--j] > key)
{
if (j == low){
break;
}
}
if (i >= j) break;
/*交换i,j对应的值*/
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/*中枢值与j对应值交换*/
int temp = arr[low];
arr[low] = arr[j];
arr[j] = temp;
Qsort(arr, low, j - 1);
Qsort(arr, j + 1, high);
}
int main()
{
int num;
cin>>num;
int a[num];
for(int i=0;i<num;i++)cin>>a[i];
Qsort(a,0,num-1);/*这里原文第三个参数要减1否则内存越界*/
for(int i=0;i<num;i++)cout<<a[i]<<' ';
return 0;
}
问题 G: 算法:折半插入排序
题目描述
折半插入排序同样是一种非常简单的排序方法,它的基本操作是在一个已经排好序的有序表中进行查找和插入。不难发现这个查找的过程可以十分自然的修改成折半查找的方式进行实现。
折半插入排序的算法可以描述如下:
在本题中,读入一串整数,将其使用以上描述的折半插入排序的方法从小到大排序,并输出。
输入
输入的第一行包含1个正整数n,表示共有n个整数需要参与排序。其中n不超过1000。
第二行包含n个用空格隔开的正整数,表示n个需要排序的整数。
输出
只有1行,包含n个整数,表示从小到大排序完毕的所有整数。
请在每个整数后输出一个空格,并请注意行尾输出换行。
样例输入
102 8 4 6 1 10 7 3 5 9
样例输出
1 2 3 4 5 6 7 8 9 10
提示
在本题中,需要按照题目描述中的算法完成折半插入排序的算法。与直接插入排序算法不同,折半插入排序算法在查找插入位置时采用了折半查找的方案,减少了关键字之间的比较次数,但是记录的移动次数并没有发生改变,因此折半插入排序的时间复杂度依旧为O(n2),同样不是一种非常高效的排序方法。
代码
#include<iostream>
using namespace std;
void BInsertSort(int n,int a[])
{
int mid,length=n;
for(int i=2;i<=n;++i)
{ a[0]=a[i];
int low=1,high=i-1;
while(low<=high)
{
mid=(low+high)/2;
if(a[0]<a[mid])
{high=mid-1;}
else
{low=mid+1;}
}
for(int j=i-1;j>=high+1;--j)
{a[j+1]=a[j];}
a[high+1]=a[0];
}
}
int main()
{ int num;
cin>>num;
int a[1001];
for(int i=1;i<=num;i++)
{cin>>a[i];}
BInsertSort(num,a);
for(int i=1;i<=num;i++)
{cout<<a[i]<<" ";}
return 0;
}