一、用List类实现一元多项式的表示与相加
1、链表的实现
结构体Node
作为链表结点,包含指针next
与两个整型元素:value
系数和index
指数。
List
类包含头指针,头指针指向头结点,头结点默认元素为0。(加上头结点真的可以简化很多操作,insert、delete等操作不需将首尾单独实现)各个结点根据指数由低到高排列。
List
类的成员函数包括构造函数、析构函数、插入元素、删除元素、查找元素、两个链表的相加。
struct Node{
Node* next;
int value,index;
Node(int val=0,int ind=0):value(val),index(ind),next(NULL){}
};
class List{
public:
Node* head;
List() {
head = new Node();
head->next=NULL;
}
~List(){
while(head != NULL){
Node *temp=head;
head=head->next;
delete temp;
}
}
void clear(){
while(head != NULL){
Node *temp=head;
head=head->next;
delete temp;
}
head = new Node();
head->next=NULL;
}
void Insert(Node* n);
void DeleteElement(int k);
Node* SearchkthNode(int k);
void MergeList(List &List1, List &List2);
};
2、具体函数实现
(1)链表元素插入:
比较新加入结点n
的指数,确定该结点插入的位置,进行插入。
void List::Insert(Node* n){
if(n->value==0)return;
Node *temp=head;
while(temp->next != NULL){
if(temp->next->index >= n->index) break;
temp=temp->next;
}
if(temp->next==NULL || temp->next->index > n->index) {
Node *p= temp->next;
temp->next=n;
n->next=p;
}
else {
temp->next->value += n->value;
delete n;
}
return;
}
(2)链表元素删除:
找到第k个结点,将其删除。(第一个元素定义为结点1,以此类推)
void List::DeleteElement(int k){
if(k<=0)return;
Node *temp=head;
for(int i=0;i<k-1 && temp != NULL;i++){
temp=temp->next;
}
if(temp == NULL || temp->next==NULL)return;
Node *p=temp->next;
temp->next=p->next;
delete p;
return;
}
(3)链表元素查找:
查找第k个结点,返回该结点的指针,若该结点不存在,返回NULL
。
Node* List::SearchkthNode(int k){
if(k<=0)return NULL;
Node *temp=head;
for(int i=0;i<k && temp != NULL;i++){
temp=temp->next;
}
return temp;
}
(4)两个链表进行相加:
两个链表list1
、list2
作为参数传入,进行成员函数操作的链表作为相加的结果。
首先,如果该链表不为空,则先将链表清空,以便插入操作。将p1
p2
分别指向两个源链表的第一个元素(注意不是头结点),比较两个结点的指数大小,将较小的结点插入新链表尾部;若两个结点指数相等,则将两个结点的系数相加后进行插入。每进行一次插入操作,p1
p2
向后移一位,直到指向NULL
。
进行结点操作时注意我们的链表是有头结点的,不需要对头结点进行操作。
void List::MergeList(List &list1, List &list2){
if(head != list1.head && head != list2.head){
this->clear();
}
Node *newhead,*p1=list1.head->next,*p2=list2.head->next;
newhead = new Node();
Node *t=newhead;
while(p1 != NULL && p2 != NULL){
if(p1->index>p2->index){
Node *newone=new Node(p2->value,p2->index);
t->next=newone;
t=t->next;
p2=p2->next;
}
else if(p1->index<p2->index){
Node *newone=new Node(p1->value,p1->index);
t->next=newone;
t=t->next;
p1=p1->next;
}
else {
if(p1->value+p2->value!=0){
Node *newone=new Node(p1->value+p2->value,p1->index);
t->next=newone;
t=t->next;
}
p1=p1->next;
p2=p2->next;
}
}
while(p2!=NULL){
Node *newone=new Node(p2->value,p2->index);
t->next=newone;
t=t->next;
p2=p2->next;
}
while(p1!=NULL){
Node *newone=new Node(p1->value,p1->index);
t->next=newone;
t=t->next;
p1=p1->next;
}
head=newhead;
return;
}
3、main测试函数
main函数利用srand((int)time(NULL));
与rand()%int
生成随机数进行测试,注意包含头文件#include<windows.h>
、#include<time.h>
。
int main(){
List list1,list2;
Node *n1,*n2;
srand((int)time(NULL));
for(int i=0;i<10;i++){
n1=new Node(rand()%20,rand()%20);
list1.Insert(n1);
}
cout<<"list1=";
Node *t=list1.head->next;
while(t!=NULL){
cout<<t->value<<"x^"<<t->index;
t=t->next;
if(t!=NULL)cout<<" + ";
}
cout<<endl;
for(int i=0;i<10;i++){
n1=new Node(rand()%20,i);
list2.Insert(n1);
}
cout<<"list2=";
t=list2.head->next;
while(t!=NULL){
cout<<t->value<<"x^"<<t->index;
t=t->next;
if(t!=NULL)cout<<" + ";
}
cout<<endl;
List n;
n.MergeList(list1,list2);
t=n.head->next;
cout<<"n=";
while(t!=NULL){
cout<<t->value<<"x^"<<t->index;
t=t->next;
if(t!=NULL)cout<<" + ";
}
}
——2019-09-06
二、链表排序
给定一个链表和一个值X,操作使得节点值小于X的节点都在大于等于X的节点的前面,并保持每个分区节点的相对顺序不变。
-
输入样例 3
6->5->4->3->2->1->5->0 -
输出样例 2->1->0->6->5->4->3->5
1、链表结构
struct linkNode {
int val;
linkNode *next;
linkNode(){
val=0;
next=NULL;
}
linkNode(int x) : val(x), next(NULL) {}
~linkNode(){}
};
linkNode* partList(linkNode *head, int x);
2、排序函数的实现
linkNode* partList(linkNode *head, int x)
函数用于创造一个完成排序的链表,返回其头指针。
排序中最重要的部分是保持其相对顺序不变,实际上可以简单理解成将链表分割成两个子链表,每个子链表中元素的相对顺序与源链表相同,只要在最后将两个子链表连接,就可以得到排序后的链表。
源链表:6->5->4->3->2->1->5->0 (x=3)
子链表一:2->1->0
子链表二:6->5->4->3->5
重新连接:2->1->0->6->5->4->3->5
linkNode* partList(linkNode *head, int x){
linkNode *p=head;
linkNode *p1=new linkNode(0); //子链表一,为其创建头结点
linkNode *p2=new linkNode(0);//子链表二,为其创建头结点
linkNode *h1=p1,*h2=p2; //需要记录子链表的头结点,且每次都在尾结点插入新元素
while(p!=NULL){ //遍历整个链表
if(p->val < x){
p1->next=new linkNode(p->val); //若结点数值比x小,给子链表一插入元素
p1=p1->next;
}
else {
p2->next=new linkNode(p->val);//若结点数值大于等于x,给子链表二插入元素
p2=p2->next;
}
p=p->next;
}
p1->next=h2->next;
return h1->next; //注意子链表一、子链表二都有头结点,返回时需要跳过
}
3、main测试函数
int main(){
linkNode *head=NULL;
srand((int)time(NULL));
for(int i=0;i<10;i++){
linkNode *n1=new linkNode(rand()%20);
n1->next=head;
head=n1;
}
linkNode *head1=partList(head,10);
while(head!=NULL){
cout<<head->val<<endl;
head=head->next;
}
cout<<endl;
while(head1!=NULL){
cout<<head1->val<<endl;
head1=head1->next;
}
}
——2019-09-12
三、输入输出受限的双向队列
1、题目信息
若以1234作为双端队列的输入序列,分别求出满足下列条件的输出序列:
(1)能由输入受限的双端队列得到,但是不能由输出受限的双端队列得到的输出序列;
(2)能由输出受限的双端队列得到,但是不能由输入受限的双端队列得到的输出序列;
(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。
2、基本思路
由于不想手动枚举,故想到了写一个程序遍历显示所有的情况,再比较各个情况的不同部分。基本思路是先构造一个输入输出不受限的双端链表,将1、2、3、4按顺序输入,将每一次输出依次计入一个4元素的数组中,当数组满了之后就把数组打印出来,就能得到当前情况所对应的全排列。当需要输入/输出受限时,将对应的代码注释掉即可。
为了得到不受限的所有遍历,可以利用函数调用得到所有的分支,每一次的函数调用都有队首输入、队尾输入、队首输出、队尾输出四个子调用;而当用于记录的数组满了,就打印出数组,逻辑流程图如下:
最后我们得到的函数为void fun(int *arr,int n,deque<int> num)
,但首先我们看看main函数:
#include<iostream>
#include <deque>
using namespace std;
int arr0[4][24];
int main(){
deque<int> num;
int arr[4]={0,0,0,0};
for(int i=0;i<4;i++){
for(int j=0;j<24;j++){
arr0[i][j]=0;
}
}
fun(arr,1,num);
for(int i=0;i<24;i++){
cout<<arr0[0][i]<<arr0[1][i]<<arr0[2][i]<<arr0[3][i]<<endl;
}
}
我们定义了一个全局变量int arr0[4][24]
,这是为了储存所得到的所有不同序列,由于1234的全排列有$4!=24$种,则二维数组大小为$24\times4$,若该次遍历所得到的排列与二维数组中排列不同,则存入该数组,最后再将整个数组输出。
最后是函数的实现:
void fun(int *arr,int n,deque<int> num){
int arr1[4],arr2[4],arr3[4],arr4[4];
int i=0;
for(;i<4;i++){
arr1[i]=arr[i];
arr2[i]=arr[i];
arr3[i]=arr[i];
arr4[i]=arr[i];
}
//数组创建
if(arr[3] != 0){
int diff=1;
for(int i=0;i<24;i++){
if(arr0[0][i]==arr[0] && arr0[1][i]==arr[1] && arr0[2][i]==arr[2] && arr0[3][i]==arr[3] ){
diff=0;
break;
}
}
int i=0;
for(;i<24;i++){
if(arr0[0][i]==0)break;
}
if(diff){
arr0[0][i]=arr[0];
arr0[1][i]=arr[1];
arr0[2][i]=arr[2];
arr0[3][i]=arr[3];
}
return;
}
//数组已满时,判断是否与已得排列重复,若不重复,写入二维数组中
if(n<5){
num.push_front(n);
fun(arr1,n+1,num);
num.pop_front();
}
//队首输入
if(n<5){
num.push_back(n);
fun(arr2,n+1,num);
num.pop_back();
}
//队尾输入
if(!num.empty()){
int i=0;
for(;i<4;i++){
if(arr3[i]==0)break;
}
arr3[i]=num.front();
num.pop_front();
fun(arr3,n,num);
num.push_front(arr3[i]);
}
//队首输出
if(!num.empty()){
int i=0;
for(;i<4;i++){
if(arr4[i]==0)break;
}
arr4[i]=num.back();
num.pop_back();
fun(arr4,n,num);
num.push_back(arr4[i]);
}
//队尾输出
return;
}
第一步首先创建了四个新数组,这是因为函数调用数组是以指针的形式,并没有创建新的数组,所以我们需要手动创建;同时在对队列进行输入输出时,进行每一次输入输出、调用子函数后应将队列复原至原来的情况,以保证对每一个函数的调用都是只针对一种情况。
3、运行结果
执行输入输出的双端队列,得到的是24个全排列:
将其中一个输入注释掉,即输入受限,得到的结果少了4231、4213:
将其中一个输出注释掉,即输出受限,得到的结果少了4132、4231:
那我们得到的答案为:
-
能由输入受限的双端队列得到,但是不能由输出受限的双端队列得到的输出序列:
4132 -
能由输出受限的双端队列得到,但是不能由输入受限的双端队列得到的输出序列:
4213 -
既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列:
4231
4、Todo
- 将固定输出改为由用户输入n,生成n个数字生成的序列。
- 输出得到的排列数顺序不固定,将输出按照一定的顺序排列再输出。
——2019-09-19
四、吃豆人
有N个级别互不相同的吃豆人在一条街道的不同位置上向左或向右移动,两个吃豆人碰面时级别高的能够吃掉级别低的,吃豆人移动的速度相同,求问足够长时间过后能够存活多少个吃豆人。
-
输入格式: 第一行N代表吃豆人的数量,N在1到99999之间。 随后N行,每行2个数A,B为从左到右排列的N个吃豆人的级别和吃豆人移动的方向,A在1到99999之间,B为0表示向左,1表示向右。
-
输入 5 4 0 3 1 2 0 1 0 5 0
-
输出 2
1、算法思路
这道题的思路比较难,我们首先考虑两个吃豆人的情况:由于速度相同,那么←←、→→、←→的情况都不会相遇,只有在→←才可能相遇。
而在→←相遇时,等级高的会吃掉等级低的,若剩下←,这个吃豆人将不会再被吃掉(假设新的吃豆人从右边出现);若剩下→,那接下来出现的吃豆人将有可能再次构成→←进行操作。
根据这个思路,我们可以用栈结构完成这道题,将吃豆人从左到右依次压栈,当出现→←的情况时,将需要入栈的吃豆人与栈顶的吃豆人比较,当等级低时弹出,直到栈顶的吃豆人比当前吃豆人等级高,则比较下一吃豆人(或者栈空或出现←的吃豆人,则将当前吃豆人入栈);直到所有吃豆人都入栈之后,统计当前栈内吃豆人的个数,则是最终结果。
2、代码实现
#include <iostream>
#include <stack>
using namespace std;
struct pa{
int dec;
int lev;
}; //吃豆人结构体
int main()
{
int n;
cin >> n;
stack<pa> sta;
pa a;
cin>>a.lev>>a.dec;
sta.push(a); //将第一个吃豆人入栈,防止下面的判断出现栈空
for(int i=1;i<n;i++){ //从第二个吃豆人开始
pa p;
cin>>p.lev>>p.dec;
if(p.dec==0 && sta.top().dec==1){ //→←的情况
while(!sta.empty() && sta.top().dec==1 && sta.top().lev<p.lev ){
sta.pop();
}//循环直到→←的情况结束
if(sta.empty() || sta.top().dec==0)
sta.push(p); //若结束后当前的吃豆人没被栈顶的吃掉,入栈
}
else sta.push(p); //其他情况,直接压栈即可
}
int num=0;
while(!sta.empty()){
sta.pop();
num++;
}
cout << num;
}
——2019-10-07
五、括号匹配
给定一个字符串,确认该字符串中的括号是否合法,如果合法输出True,不合法输出False。 判定规则,只有形如(),[],{}这样的格式才算合法,可以嵌套括号。
没什么难度,直接栈实现,若右括号无法匹配栈中的左括号/最后栈不空,则输出False。
代码如下:
#include<iostream>
#include<string>
using namespace std;
int main(){
string x;
cin>>x;
int arr[x.size()]; //数组做栈
int n=0; //n为元素个数,n-1指向栈顶
for(int i=0;i<x.size();i++){
switch(x[i]){
case '(':{
arr[n]=0;
n++;
break;
}
case '[':{
arr[n]=1;
n++;
break;
}
case '{':{
arr[n]=2;
n++;
break;
}
case ')':{
if(arr[n-1]==0){
n--;
}
else{
cout<<"False";
return 0;
}
break;
}
case ']':{
if(arr[n-1]==1){
n--;
}
else{
cout<<"False";
return 0;
}
break;
}
case '}':{
if(arr[n-1]==2){
n--;
}
else{
cout<<"False";
return 0;
}
break;
}
}
}
if(n==0)cout<<"True";
else cout<<"False";
}
class Solution:
def isValid(self, s: str) -> bool:
self.x = []
for i in range(0,len(s)):
if s[i]=='(' or s[i]=='{' or s[i]=='[':
self.x.append(s[i])
elif s[i]==')':
if self.x==[] or self.x.pop() != '(':
return False
elif s[i]=='}':
if self.x==[] or self.x.pop() != '{':
return False
elif s[i]==']':
if self.x==[] or self.x.pop() != '[':
return False
return self.x ==[]
这道题写出来主要是为了下一道题服务。
——2019-10-09
六、最长括号子串匹配
给定一个只包含小括号的字符串,求出其中最长的有效的括号的子串的长度。
-
输入 )()()(
-
输出 4
-
输入 (()
-
输出 2
1、算法思路
这种有效括号的题目很容易想到使用栈stack来处理,但是这题的难点是需要找到一个明晰的思路,不然很容易逻辑混乱。解决思路为使用一个变量start来记录最初有效字串的起始下标,这里比较值得注意的是栈中保存的不是括号字符而是括号的位置的值,这样做的目的是为了后面计算长度:
思路如下:
-
需有一个变量start记录有效括号子串的起始下标,max_len表示最长有效括号子串长度,初始值均为0
-
遍历给字符串中的所有字符 1.需有一个变量start记录有效括号子串的起始下标,max_len表示最长有效括号子串长度,初始值均为0
2.遍历给字符串中的所有字符
2.1若当前字符s[index]为左括号'(',将当前字符下标index入栈(下标稍后有其他用处),处理下一字符
2.2若当前字符s[index]为右括号')',判断当前栈是否为空
2.2.1若栈为空,则start = index + 1,处理下一字符(当前字符右括号下标不入栈)
2.2.2若栈不为空,则出栈(由于仅左括号入栈,则出栈元素对应的字符一定为左括号,可与当前字符右括号配对),判断栈是否为空
2.2.2.1若栈为空,则max_len= max(max_len, index-start+1)
2.2.2.2若栈不为空,则max_len= max(max_len, index-栈顶元素值)
2、代码实现
#include<iostream>
#include<string>
#include <stack>
using namespace std;
int Max(int a,int b){
return a>b?a:b;
}
int main(){
string str;
cin>>str;
int count=str.size(),max=0,start=0;
stack<int> num;
for(int i=0;i<count;i++){
if(str[i]=='(')
num.push(i);
else {
if(num.empty()){
start=i+1;
}
else {
num.pop();
if(num.empty()){
max=Max(max,i-start+1);
}
else max=Max(max,num.top()-start);
}
}
}
cout<<max;
}
——2019-10-12
七、坐缆车
1、问题描述
M山上设置了n个只下山不上山的缆车点1,2…n。游客可以从山上的某一点上缆车,并从其下方任意点下缆车。缆车点i到缆车点j之间的费用为r(i,j),1<=i<=j<=n。试计算从山顶1到山脚n坐缆车所需的最少费用。
输入格式:
第一行一个正整数n,表示有n个缆车点,接下来n-1行是一个半矩阵,当前行表示当前点按顺序到其下方点的费用r(i,j),从点1开始。
输出格式:
一个整数,表示最小费用
2、算法思路
本题就是一个简单的Dijkstra算法实现,给定n个缆车点和费用,图的结构和信息即得出,利用邻接矩阵存储,并进行Dijkstra运算得出最小费用即可。
3、代码实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n;
int main(){
cin>>n;
int Mat[n][n];
for(int i=0;i<n;i++){
for(int j=1+i;j<n;j++){
cin>>Mat[i][j];
}
}
int visit[n],path[n];
visit[0]=0;
for(int i=1;i<n;i++){
visit[i]=Mat[0][i];
}
for(int i=1;i<n;i++){
int min=visit[i],min_ind=i;
for(int j=i;j<n;j++){
if(min>visit[j]){
min=visit[j],min_ind=j;
}
}//找最小值
for(int k=min_ind+1;k<n;k++){
int temp=visit[min_ind]+Mat[min_ind][k];
visit[k]=visit[k]<temp?visit[k]:temp;
}//最短路径
}
cout<<visit[n-1];
}
八、一笔画
1、问题描述
给定一个无向图,包含n 个顶点(编号1~n),m条边,求最少用多少笔可以画出图中所有的边,一条边不会被描绘多次。
输入格式:
第一行2个数n,m,接下来m行,每行两个数a,b表示a,b两点之间有一条边相连。
输出格式:
一个数,表示多少笔。
2、算法思路
一笔画问题只需要判断图的各个结点的度的奇偶性即可,画的笔数为奇数点个数/2。
但是上面只是针对一个连通图得到的解题方法,当图中有多个连通分支时,需要根据每个连通分支判断。
3、代码实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n,m;
int main(){
cin>>n>>m;
int count[n];
for(int i=0;i<n;i++){
count[i]=0;
}
for(int i=0;i<m;i++){
int t1,t2;
cin>>t1>>t2;
count[t1-1]++;
count[t2-1]++;
}
int k=0;
for(int i=0;i<n;i++){
if(count[i]%2==1){
k++;
}
}//k为奇数点个数
if(k==0)k=2;//当没有奇数点,只需一笔
cout<<k/2;
}
代码只能得出连通图的一笔画判断,多个连通分支的判断尚未实现。
九、食物链
十、赛马问题
1、问题描述
A与B之间将进行一场赛马比赛,C为裁判。A与B分别拥有n匹马,这2n匹马中每匹马拥有的能力值都不相同。比赛前,参赛的两人先决定自己的马的出场顺序;比赛时,A的第一匹马将对战B的第一匹马,A的第二匹马将对战B的第二匹马,以此类推。在每一轮的比赛当中,能力值较高的马将获得胜利,并记其拥有者加1分。进行过n轮比赛之后,得分较高的人将获得最终的胜利,并赢得所有的马。当然,可能存在平局的情况,此时算作裁判C胜利,并获得所有的马。
问:给定每一匹马的能力值,裁判C能否通过重新调整马匹参赛顺序而获得胜利?
输入:
第一行输入一个整数n,1 <= n <= 100。
第二行输入n个整数,代表选手A所有马匹的能力值。
第二行输入n个整数,代表选手B所有马匹的能力值。
输出:
如果可以平局的话,则输出“YES”,否则输出“NO”。
2、算法思路
裁判C胜利的条件为AB平局,那么,我们可以先将AB的马排序,让A能力值最高的一半与B能力值最低的一半比赛,反之让B能力值最高的一半与A能力值最低的一半比赛,确保A、B均有一半胜出。
而且,在这两组比较中,我们也要确保将A、B的马按顺序比较,例如A:1,2,3,4;B:3,4,5,6;依次比较1-3、2-4、3-5、4-6,才能确保B获得所有胜利。
【不能只比较A、B组中最好、最差的马,比如上述例子会出现判断出错】
3、代码实现
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
int n;
cin>>n;
int A[n],B[n];
for(int i=0;i<n;i++){
cin>>A[i];
}
for(int i=0;i<n;i++){
cin>>B[i];
}
for(int i=0;i<n-1;i++){
for(int j=i+1;j<n;j++){
if(A[i]>A[j]){
int temp=A[i];
A[i]=A[j];
A[j]=temp;
}
if(B[i]>B[j]){
int temp=B[i];
B[i]=B[j];
B[j]=temp;
}
}
}//对AB进行排序
int yes=1;
int mid1=n/2,mid2=mid1+1;//分成两组
for(int i=0;i<mid1;i++){
if(A[i]>B[i+mid1]){
yes=0;
}
if(B[i]>A[i+mid1]){
yes=0;
}
}
if(yes)cout<<"YES";
else cout<<"NO";
return 0;
}