大学《数据结构》实验总结

实验一.线性表的基本操作及应用

1.单链表的基本操作

(1)依次从键盘读入数据,建立带头结点的单链表;
(2)输出单链表中的数据元素
(3)求单链表的长度;
(4)根据指定条件能够取元素和修改元素;
(5)实现在指定位置插入和删除元素的功能。

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
typedef int ElemType;
typedef struct node{
    ElemType data;
    struct node *next;
}List;
int sum;//当前节点总数,即链表长度
bool init(List *&L,List *&r)//初始化链表
{
    L=new node;
    if(!L)return false;//申请空间失败
    L->next=NULL;
    r=L;//尾指针r
    sum=0;
    return true;
}
void create_list(List *&r)//创建链表
{
    ElemType x;
    while(1)
    {
        scanf("%d",&x);
        if(x==-1)break;//以-1结束输入(自己定义的约定)
        ++sum;
        List *p=new List;
        p->data=x;p->next=NULL;
        r->next=p;//尾插法
        r=p;
    }
}
void print(List *L)//打印链表数据
{
    List *p=L->next;
    int index=0;
    while(p)
    {
        printf("第%d个节点的数据是:%d\n",++index,p->data);
        p=p->next;
    }
}
void getlen(){//返回链表长度
    printf("该单链表长度为: %d\n",sum);
}
ElemType query_list_index(List *L,int id)//根据id查询
{
    List *p=L->next;
    if(id<1||id>sum)return inf;
    int pos=1;
    while(pos!=id){
        p=p->next;
        ++pos;
    }
    return p->data;
}
int query_list_value(List *L,ElemType v)//根据值查询
{
    List *p=L->next;
    int pos=1;
    while(p){
        if(p->data==v)return pos;
        ++pos;
        p=p->next;
    }
    return inf;//没找到返回无效值便于判断
}
bool update_list(List *L,int id,ElemType v)//根据id修改
{
    List *p=L->next;
    if(id<1||id>sum)return false;
    int pos=1;
    while(pos!=id){
        p=p->next;
        pos++;
    }
    p->data=v;
    return true;
}
bool insert_list(List *L,int pos,ElemType v)//插入第pos结点前
{
    List *p=L;
    int j=0;
    while(p&&j<pos-1){//找目标节点的前驱
        p=p->next;++j;
    }
    if(!p||j>pos-1)return false;
    List *q=new List;
    q->data=v;
    q->next=p->next;
    p->next=q;
    ++sum;//链表长度加一
    return true;
}
bool delete_list(List *L,int pos)//删除第pos个节点
{
    List *p=L;
    if(pos<1||pos>sum)return false;
    else {
        int j=0;
        while(p->next&&pos-1>j)//依然找前驱
        {
            p=p->next;
            j++;
        }
        if(!(p->next)||pos-1<j)return false;
        sum--;
        List *q=p->next;
        p->next=p->next->next;
        delete q;
        return true;
    }
}
int main()
{
    List *L,*r;
    init(L,r);
    create_list(r);
    print(L);
    //update_list(L,3,5);
    //cout<<query_list_value(L,3)<<'\n';
    //insert_list(L,2,9);//在第二节点前添加一个节点
    //delete_list(L,1);//删除第一个节点
    print(L);
    return 0;
}
2.约瑟夫环

【问题描述】
  约瑟夫(Joseph)问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计一个程序求出出列顺序。
【基本要求】
  利用单向循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。
【测试数据】
由学生任意指定。
  如:m的初值为20;n的值为7;密码:3,1,7,2,4,8,4;
(正确的输出结果应为6,1,4,7,2,3,5)。
(报告上要求写出多批数据测试结果)
【实现提示】
  程序运行后首先要求用户输入初始报数上限值m,人数n,(设n≤30)。然后输入各人的密码。
【选作内容】
  向上述程序中添加在顺序结构上实现的部分。

【算法设计】
模拟约瑟夫环,进行频繁的节点删除,所以第一步就是先找到要删除的节点的前驱,然后将要删除的节点的id存入出队链表中,然后将其删除,同时将当前密码更新为出队节点的密码,并将出队节点的后继标记为1号,继续循环找下一个要出队的人,直到原链表为空。
坑点: 要注意头节点,遇到头节点要跳过。

1.链表模拟写法

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
//typedef int ElemType;
typedef struct node{
    int pw,id;
    struct node *next;
}List;
int n,m;
void init(List *&L,List *&r)//初始化链表
{
    L=new node;
    L->pw=-1;L->id=0;
    L->next=L;
    r=L;//队尾指针
}
void create_list(List *&L,List *&r,int n)//创建链表
{
    int pw;
    for(int i=1;i<=n;i++)//尾插法建表
    {
        scanf("%d",&pw);
        List *p=new List;
        p->id=i,p->pw=pw,p->next=L;
        r->next=p;
        r=p;
    }
}
void print(List *ans)//打印出列顺序
{
    List *p=ans->next;
    printf("出队顺序为: ");
    while(p)
    {
        printf("%d ",p->id);
        p=p->next;
    }
    printf("\n");
}
void joseph(List *&L,int pw,List *&r){
    List *p=L->next,*qq=L;//p指向当前节点,qq指向p的前驱节点
    int num=1,stop=0;//stop为出队了几个人
    int now=pw;//最开始的密码
    while(stop<n){
        if(num==now){
            stop++;num=1;
            List *q=new List;//ans的新添节点
            q->id=p->id;q->next=NULL;
            r->next=q;
            r=q;
            List *d=new List;//要删除的节点,当前要出队的人
            List *s=new List;//删除节点后要做的调整
            now=p->pw;
            d=qq->next;
            s=qq;
            s->next=s->next->next;
            delete d;
            qq=p;p=p->next;
            if(p==L){//头节点要跳过
                q=p;p=p->next;
            }
        }
        else {
            if(p->next==L)p=p->next;//头节点要跳过
            qq=p;p=p->next;
            num++;
        }
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        List *L,*r;//模拟约瑟夫环的链表
        init(L,r);//初始化链表
        create_list(L,r,n);
        List *ans;//出列的顺序存入ans链表
        init(ans,r);//初始化链表
        joseph(L,m,r);
        print(ans);
    }
    return 0;
}

2.顺序表模拟写法

#include <bits/stdc++.h>
using namespace std;
const int maxn=50;
int a[maxn],b[maxn];
int b1=0;//第几个出队的人
int n,m;

void print()//打印出列顺序
{
    printf("出队顺序为: ");
    for(int i=0;i<n;i++){
        printf("%d%c",b[i],i==n-1?'\n':' ');
    }
}
void joseph(){//数组从0开始,便于取余来模拟循环
    int pw=m;
    int num=0,pos=0,stop=0;
    while(stop<n){
        if(num+1==pw){
            ++stop;
            b[b1++]=pos+1;//出队人顺序记录在b数组
            pw=a[pos];//密码重置
            a[pos]=-1;//出队
            num=0;
            while(a[pos]==-1){//找到下一个没出队的人
                if(stop==n)break;
                pos=(pos+1)%n;
            }
        }
        else {
            ++num;pos=(pos+1)%n;
            while(a[pos]==-1)pos=(pos+1)%n;
        }
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        b1=0;
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        joseph();
        print();
    }
    return 0;
}
3.多项式的表示及相加

【问题描述】
设计一个算法,以实现一元稀疏多项式的加法运算。
【基本要求】
(1)输入并建立多项式;
(2)输出多项式,输出形式为整数序列:n,c1,e1,c2,e2,……,cn,en,其中n是多项式的项数,ci和ei分别是第i项的系数和指数,序列按指数降序排列;
(3)多项式a和b相加,建立多项式a+b。
【测试数据】
由学生任意指定。
【实现提示】
  用带表头结点的单链表存储多项式。
【选作内容】
  多项式a和b相减,建立多项式a-b。

【算法设计】
规定输入的两个表达式的各项指数都严格递减(偷懒),然后用第三个链表来存计算结果.我的思路是先选择一个表达式,枚举每个节点的同时,枚举一遍另一个表达式的节点,以保证结果各项指数严格递减.处理手段可以自己想.

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3;
int n,m,c,e;
typedef struct node{
    int c,e;//系数与指数
    struct node *next;
}List;
void init(List *&L1,List *&r){//初始化带头节点的链表
    L1=new List;
    L1->c=-1,L1->e=-1,L1->next=NULL;
    r=L1;
}
void create_List(int c,int e,int &n,List *&r){//尾插法构造链表
    ++n;
    List *p=new List;
    p->c=c,p->e=e,p->next=NULL;
    r->next=p;
    r=p;
}
void print(List *L1,List *L2,int len1,int len2){//分别打印两个多项式数据
    printf("第一的多项式长度为: %d\n",len1);
    List *p=L1->next;
    while(p){
        printf("%d %d\n",p->c,p->e);
        p=p->next;
    }
    printf("第二的多项式长度为: %d\n",len2);
    p=L2->next;
    while(p){
        printf("%d %d\n",p->c,p->e);
        p=p->next;
    }
}
void get_add(List *L1,List *L2,List *&r){//多项式相加
    int vis[maxn];//为保证计算结果高项在前,该数组用于记录第二个多项式的各项是否被用过
    memset(vis,0,sizeof(vis));//初始化为0,表示一开始都未访问过
    List *p1=L1->next;//p1开始指向首元节点
    while(p1){
        int c=p1->c,e=p1->e;
        List *p=L2->next;//p用于遍历第二个多项式
        int num=1,flag=0;
        while(p){
            if(p->e>e&&!vis[num]){
                vis[num]=1;
                List *add=new List;
                add->c=p->c,add->e=p->e,add->next=NULL;
                r->next=add;r=add;
            }
            else if(p->e==e){
                flag=1;
                vis[num]=1;
                c=c+p->c;
                if(c!=0){
                    List *add=new List;
                    add->c=c,add->e=e,add->next=NULL;
                    r->next=add;r=add;
                }
                break;
            }
            ++num;p=p->next;
        }
        if(!flag){//表明p1当前节点为目前最大项,且在第二个多项式中未找到与其同指数的项
            List *add=new List;
            add->c=c,add->e=e,add->next=NULL;
            r->next=add;r=add;
        }
        p1=p1->next;
    }
}
void get_reduce(List *L1,List *L2,List *&r){//多项式相减,与相加操作相似,只有部分做了改动
    int vis[maxn];
    memset(vis,0,sizeof(vis));
    List *p1=L1->next;
    while(p1){
        int c=p1->c,e=p1->e;
        List *p=L2->next;int num=1,flag=0;
        while(p){
            if(p->e>e&&!vis[num]){
                vis[num]=1;
                List *add=new List;
                add->c=-p->c,add->e=p->e,add->next=NULL;
                r->next=add;r=add;
            }
            else if(p->e==e){
                flag=1;
                vis[num]=1;
                c=c-p->c;
                if(c!=0){
                    List *add=new List;
                    add->c=c,add->e=e,add->next=NULL;
                    r->next=add;r=add;
                }
                break;
            }
            ++num;p=p->next;
        }
        if(!flag){
            List *add=new List;
            add->c=c,add->e=e,add->next=NULL;
            r->next=add;r=add;
        }
        p1=p1->next;
    }
}
void print_ans(List *ans,int op){//打印多项式运算后的结果,规范输出
    List *p=ans->next;
    if(op==1)printf("多项式相加后结果为: ");
    else printf("多项式相减后结果为: ");
    int flag=1;
    while(p){
        if(flag!=1){
            if(p->c>0)printf("+");
        }
        printf("%dx%d",p->c,p->e);
        ++flag;
        p=p->next;
    }
    putchar('\n');
}
int main()
{
    List *L1,*r1;int n1=0;
    init(L1,r1);
    char pre='0';
    //为了方便处理,不妨假定输入指数顺序为降序
    while(scanf("%dx%d",&c,&e)){//第一个多项式输入
        char s=getchar();
        if(pre=='-')c=-c;
        if(s=='-')pre='-';
        else pre='+';
        create_List(c,e,n1,r1);
        if(s=='\n')break;
    }
    List *L2,*r2;int n2=0;
    init(L2,r2);
    pre='0';
    while(scanf("%dx%d",&c,&e)){//第二个多项式输入
        char s=getchar();
        if(pre=='-')c=-c;
        if(s=='-')pre='-';
        else pre='+';
        create_List(c,e,n2,r2);
        if(s=='\n')break;
    }
    print(L1,L2,n1,n2);//打印两个多项式
    List *ans,*r3;//存相加后结果的链表与尾指针
    init(ans,r3);
    get_add(L1,L2,r3);
    print_ans(ans,1);
    List *red,*r4;//存相减后结果的链表与尾指针
    init(red,r4);
    get_reduce(L1,L2,r4);
    print_ans(red,2);
    return 0;
}
4.Dr.Kong设计了一件艺术品

【问题描述】
Dr.Kong设计了一件艺术品,该艺术品由N个构件堆叠而成,N个构件从高到低按层编号依次为1,2,……,N。艺术品展出后,引起了强烈的反映。Dr.Kong观察到,人们尤其对作品的高端部分评价甚多。
狂热的Dr.Kong一激动,对组成该艺术品的N个构件重新组合,比如:把第6层到第12层的构件搬下来,想一想,然后整体放到剩下构件的第7层下面;过一会儿,又把第2层到第9层的构件搬下来,整体放到剩下构件的第1层下面等等。于是,Dr.Kong在进行了连续若干次“搬来搬去”后,还是这N个构件,又诞生了一件新的艺术品。
编程:请输出新的艺术品最高十层构件的编号。
【标准输入】
第一行: N K 表示构件的总数和“搬来搬去”的总次数
第2~K+1行:A B C 表示要搬动的构件(即从第A层到第B层)整个放在第C层下面;
如果C等于0,则要搬动的构件将放到最高层。
【标准输出】
由十行组成,分别为组成新艺术品的第一层到第十层构件的编号。
【约束条件】
(1) 10≤N≤20000 1≤k≤1000
(2) 1≤A≤B≤N, 0≤C≤N-(B-A+1)
【 样 例 】
提示:样例中仅是常规的测试数据输入及对应结果,特殊情况需要全面考虑,自己设计测试数据验证算法的健壮性。
标准输入(测试数据):
13 3
6 12 1
2 9 0
10 13 8
标准输出结果:
6
7
8
9
10
11
12
2
3
4
【实现提示】
  适宜于用带表头结点的单链表实现,涉及单个结点或连续多个结点的插入与删除。
【选作内容】
  分别用不带头结点的单链表和顺序表实现,领会三种方法的算法设计的优缺点和时间复杂度。
思路: 这道题就是整段的删除,整段的加,单点加,删会了,应该不难实现.

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3;
int n,k;
typedef struct node{
    int id;
    struct node *next;
}List;
void init(List *&L,List *&r){//初始化链表
    L=new List;
    L->id=0,L->next=NULL;
    r=L;
}
void create_list(List *&r,int n){//根据题意创建链表
    for(int i=1;i<=n;i++){
        List *p=new List;
        p->id=i,p->next=NULL;
        r->next=p;r=p;
    }
}
void get_op(List *&L,int a,int b,int c){//整段的删,整段的插
    if(a==c+1)return;
    int len=b-a+1;
    int bb=b,aa=a;
    if(c<a&&(a-c-1<len)){//尽量减少操作的次数
        b=a-1,a=c+1,c=bb;
    }
    else if(c>b&&(c-b<len)){
        a=b+1,b=c,c=aa-1;
    }
    List *m,*r;//要截取的段落
    init(m,r);
    List *p=L,*q;
    q=p;
    int num=0;
    while(p){//删除指定段落
        if(num+1==a){
            q=p;
            m->next=q->next;
            p=p->next;num=a;
            while(num!=b){
                ++num;
                p=p->next;
            }
            q->next=p->next;
            r=p;
            p->next=NULL;
            break;
        }
        else {
            ++num;
            p=p->next;
        }
    }
    p=L;num=0;
    if(c>b){
        c-=(b-a+1);
    }
    while(p){//插入指定段落
        if(num==c){
            List *s=p->next;
            q=m->next;
            p->next=q;
            r->next=s;
            break;
        }
        else {
            ++num;
            p=p->next;
        }
    }
}
void print(List *L){//打印结果
    List *p=L->next;
    int num=0;
    while(num<10){
        ++num;
        printf("第%d层构件的编号为: %d\n",num,p->id);
        p=p->next;
    }
}
int main()
{
    while(~scanf("%d%d",&n,&k)){
        List *L,*r;
        init(L,r);
        create_list(r,n);
        int a,b,c;
        for(int i=1;i<=k;i++){
            scanf("%d%d%d",&a,&b,&c);
            if(a<=c&&c<=b)puts("operation failed!!!");//无法操作的情况
            else {
                get_op(L,a,b,c);
            }
        }
        print(L);
    }
    return 0;
}

实验二.栈和队列的基本操作及应用

1.栈与队列的基本操作

【栈的基本操作】

#include <bits/stdc++.h>
using namespace std;
const int Size=100;
struct Stack{//新建一个空栈,容量为100
    int v[Size];
}s;

int top=-1;//栈顶指针
bool judge_empty()//判断栈是否为空
{
    if(top==-1)return true;
    return false;
}
bool judge_full()//判断栈是否为满
{
    if(top==Size-1)return true;
    return false;
}
void push_stack(int value)//向栈中压入元素
{
    s.v[++top]=value;
}
void pop_Stack_top(int &value)//取出栈顶元素
{
	value=s.v[top--];
}
int main()
{
    int n,m;//m小于10
    cin>>n>>m;//将10进制n转换为m进制
    while(n!=0)
    {
        if(!judge_full()){
            push_stack(n%m);
            n/=m;
        }
        else {
            cout<<"栈满"<<endl;
            break;
        }
    }
    while(!judge_empty()){
        int now;
        pop_Stack_top(now);
        cout<<now;
    }
    cout<<'\n';
    return 0;
}

【链式队列】
在这里插入图片描述 一、结构体结构

队列中储存的内容,本身是一个链表
队头和队尾只是指向这个链表的两个指针,这样就可以使用两个指针控制访问的事件

typedef struct node{//单元
    int data;
    struct node *next;
}List;

typedef struct {//头尾指针
    struct node *head;
    struct node *tail;
    int len;//队列长度
}LinkQueue;

二、初始化

下图是初始化完结构体的样子

在这里插入图片描述

void init_queue(){
    q.head=(node *)malloc(sizeof(node));
    q.tail=q.head;
    q.head->next=NULL;//不要忽视
}

三、入队

下图是插队的过程

在这里插入图片描述

void insert_tail(int v){//插入到尾部
    node *p=(node *)malloc(sizeof(node));
    p->data=v;
    p->next=NULL;
    q.tail->next=p;
    q.tail=p;
    q.len++;
}

四、出队

下图是出队的过程

在这里插入图片描述
出队的特殊情况

在这里插入图片描述

void pop(){//出队
    if(q.head==q.tail)return;
    node *p;
    p=q.head->next;
    q.head->next=p->next;
    if(p->next==NULL){//特殊情况
        q.tail->next=NULL;
    }
    free(p);
    q.len--;
}

五、判断是否为空

bool isempty(){
    if(q.len==0)return true;
    return false;
}

六、打印结果

void print(){
    node *p;
    p=q.head->next;
    while(p!=NULL){
        printf("%d\n",p->data);
        p=p->next;
    }
}

【循环队列】

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define MAXSIZE 100
typedef struct {//新建一个空队列,容量为100
    int *data;
    int head,rear;
}sqQueue;
bool init_queue(sqQueue &Q){
	Q.data=new int[MAXSIZE];
	if(!Q.data)return false;
	Q.head=Q.rear=0;
	return true;
}
bool judge_empty(sqQueue Q)//判断队列是否为空
{
    if(Q.head==Q.rear)return true;
    return false;
}
int QueueLength(sqQueue Q){//获取队列长度
	return (Q.rear-Q.head+MAXSIZE)%MAXSIZE;
}
bool judge_full(sqQueue Q)//判断队列是否为满
{
    if((Q.rear+1)%MAXSIZE==Q.head)return true;
    return false;
}
bool enqueue(sqQueue &Q,int value)//添加元素
{
	if((Q.rear+1)%MAXSIZE==Q.head)return false;
    Q.data[Q.rear++]=value;
    Q.rear%=MAXSIZE;
    return true;
}
bool pop_queue_frant(sqQueue &Q,int &e)//出队
{
	if(Q.head==Q.rear)return false;
	e=Q.data[Q.head];
	Q.head=(Q.head+1)%MAXSIZE;
	return true;
}
int Gethead(sqQueue Q){
	if(Q.head!=Q.rear)return Q.data[Q.head];
	return inf;
}
int main(){
    sqQueue Q;
    init_queue(Q);
    for(int i=1;i<=5;i++){
        enqueue(Q,i);
    }
    while(!judge_empty(Q)){
        int now;
        pop_queue_frant(Q,now);
        cout<<now<<' ';
    }
    return 0;
}
2.回文判断

【问题描述】
对于一个从键盘输入的字符串,判断其是否为回文。回文即正反序相同。如“abba”是回文,而“abab”不是回文。
【基本要求】
(1)数据从键盘读入;
(2)输出要判断的字符串;
(3)利用栈和队列对给定的字符串判断其是否是回文,若是则输出“Yes”,否则输出“No”。

【算法设计】 判断回文就是不断让头与尾进行比较,所以将所有元素依次进栈和队列,不断取出栈顶元素与队首元素比较即可。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=1e3+7;
struct sqQueue{//封装循环队列
    int *data;
    int siz;
    int head,rear;
    bool init(int si){//初始化
        siz=si;
        data=new int[siz];
        if(!data)return false;
        head=rear=0;
        return true;
    }
    bool qempty(){//判空
        if(head==rear)return true;
        return false;
    }
    bool qfull(){//判满
        if((rear+1)%siz==head)return true;
        return false;
    }
    int getsize(){//队列元素个数
        return (rear-head+siz)%siz;
    }
    bool en(int value){//进队
        if(qfull())return false;
        data[rear++]=value;
        rear%=siz;
        return true;
    }
    bool pop(){//出队
        if(qempty())return false;
        head=(head+1)%siz;
        return true;
    }
    int getfront(){//取队头元素
        if(head!=rear)return data[head];
        return inf;
    }
}q;
struct Stack{//封装栈
    int *v;
    int siz;
    int head,rear;
    bool init(int si){//初始化
        siz=si;
        v=new int[siz];
        head=rear=-1;
        if(!v)return false;
        return true;
    }
    bool sempty(){//判空
        if(head==rear)return true;
        return false;
    }
    bool sfull(){//判满
        if(head==siz)return true;
        return false;
    }
    bool push(int value){//进栈
        if(sfull())return false;
        v[++head]=value;
        return true;
    }
    bool pop(){//栈顶元素出栈
        if(sempty())return false;
        head--;
        return true;
    }
    int gettop(){//取栈顶元素
        if(!sempty())return v[head];
        return inf;
    }
}s;
char str[maxn];
int main(){
    q.init(maxn);
    s.init(maxn);
    cin>>str;
    int len=strlen(str);
    for(int i=0;i<len;i++){
        s.push(str[i]-'0');
        q.en(str[i]-'0');
    }
    int flag=1;//标记是否回文
    while(!s.sempty()){
        int stop=s.gettop();s.pop();
        int qfront=q.getfront();q.pop();
        if(stop!=qfront){
            flag=0;break;
        }
    }
    if(flag)puts("Yes");
    else puts("No");
    return 0;
}
3.商品货架管理

【问题描述】
商店货架以栈的方式摆放商品。生产日期越近的越靠近栈底,出货时从栈顶取货。一天营业结束,如果货架不满,则需上货。入货直接将商品摆放到货架上,则会使生产日期越近的商品越靠近栈顶。这样就需要倒货架,使生产日期越近的越靠近栈底。
【基本要求】
设计一个算法,保证每一次上货后始终保持生产日期越近的商品越靠近栈底。

【算法设计】 每次上货前把之前存有商品生产日期的栈元素依次压入一个临时栈,此时这个临时栈从栈顶到栈底生产日期一定是递减的,要依次上货的商品生产日期刚好是递增的,也压入这个临时栈,此时这个临时栈从栈顶到栈底仍单调递减,加货完后,把这个临时栈倒到原来的栈即可,保证原来栈单调,满足题意。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=1e4+7;
struct Stack{
    int *goods;
    int siz;
    int head,rear;
    bool init(int si){
        siz=si;
        goods=new int[siz];
        head=rear=-1;
        if(!goods)return false;
        return true;
    }
    bool sempty(){
        if(head==rear)return true;
        return false;
    }
    bool sfull(){
        if(head==siz)return true;
        return false;
    }
    bool push(int value){
        if(sfull())return false;
        goods[++head]=value;
        return true;
    }
    bool pop(){
        if(sempty())return false;
        head--;
        return true;
    }
    int gettop(){
        if(!sempty())return goods[head];
        return inf;
    }
}s,s1;
int t,n;
int main(){
    s.init(maxn);
    cin>>t;//t次上货
    for(int i=1;i<=t;i++){
        cin>>n;//每次上n件商品
        s1.init(maxn);
        while(!s.sempty()){
            s1.push(s.gettop());s.pop();
        }
        for(int j=1;j<=n;j++){//n个生产日期递增输入
            int x;cin>>x;
            s1.push(x);
        }
        while(!s1.sempty()){
            s.push(s1.gettop());
            s1.pop();
        }
    }
    while(!s.sempty()){//从栈顶到栈底输出检查是否正确
        printf("%d ",s.gettop());s.pop();
    }
    return 0;
}
4.舞伴问题

【题目描述】
假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队。跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴。若两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。
【实验提示】
先入队的男士或女士亦先出队配成舞伴。因此该问题具体有典型的先进先出特性,可用队列作为算法的数据结构。在算法中,假设男士和女士的记录存放在一个数组中作为输入,然后依次扫描该数组的各元素,并根据性别来决定是进入男队还是女队。当这两个队列构造完成之后,依次将两队当前的队头元素出队来配成舞伴,直至某队列变空为止。此时,若某队仍有等待配对者,算法输出此队列中等待者的人数及排在队头的等待者的名字,他(或她)将是下一轮舞曲开始时第一个可获得舞伴的人。
【实验要求】
利用队列实现,存储结构采用顺序或链式均可

【算法设计】思路按照实验提示模拟即可,注意下所有人都配对的情况,每轮结束需要把配对的人再装回去,再用两个临时队列就行了.

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=1e3+7;
int t;
struct man{//假设以编号区分所有人
    char sex;
    int id;
};
struct sqQueue{//封装循环队列
    man *s;
    int siz;
    int head,rear;
    bool init(int si){//初始化
        siz=si;
        s=new man[siz];
        if(!s)return false;
        head=rear=0;
        return true;
    }
    bool qempty(){//判空
        if(head==rear)return true;
        return false;
    }
    bool qfull(){//判满
        if((rear+1)%siz==head)return true;
        return false;
    }
    int getsize(){//队列元素个数
        return (rear-head+siz)%siz;
    }
    bool en(man val){//进队
        if(qfull())return false;
        s[rear++]=val;
        rear%=siz;
        return true;
    }
    bool pop(){//出队
        if(qempty())return false;
        head=(head+1)%siz;
        return true;
    }
    man getfront(){//取队头元素
        if(head!=rear)return s[head];
        return man{'0',inf};
    }
}m,f,m1,f1;
char str[maxn];
int main(){
    m.init(maxn);
    f.init(maxn);
    m1.init(maxn);//存每轮配对的男人的信息
    f1.init(maxn);//存每轮配对的女人的信息
    cin>>str;
    int len=strlen(str);
    for(int i=0;i<len;i++){//规定['a','z']为女性,['A','Z']为男性
        if(str[i]>='a'&&str[i]<='z')f.en(man{str[i],i+1});
        else m.en(man{str[i],i+1});
    }
    cin>>t;//t轮舞曲
    for(int i=1;i<=t;i++){
        while(!m.qempty()&&!f.qempty()){//模拟将要配对的过程
            m1.en(m.getfront());m.pop();
            f1.en(f.getfront());f.pop();
        }
        if(m.qempty()){
            if(!f.getsize()){
                printf("所有人都配对成功!\n\n");continue;
            }
            printf("第%d轮舞曲等待者队列现在的人数为: %d\n",i,f.getsize());
            printf("第1个等待配对者她的编号为%d\n",f.getfront().id);
        }
        else {
            if(!m.getsize()){
                printf("所有人都配对成功!\n\n");continue;
            }
            printf("第%d轮舞曲等待者队列现在的人数为: %d\n",i,m.getsize());
            printf("第1个等待配对者他的编号为%d\n",m.getfront().id);
        }
        while(!m1.qempty()){//该轮舞曲结束,所有配对的人按舞曲前顺序依次重新入队
            m.en(m1.getfront());m1.pop();
            f.en(f1.getfront());f1.pop();
        }
        putchar('\n');
    }
    return 0;
}
5.Rails

【题目描述】
There is a famous railway station in PopPush City. Country there is incredibly hilly. The station was built in last century. Unfortunately, funds were extremely limited that time. It was possible to establish only a surface track. Moreover, it turned out that the station could be only a dead-end one (see picture) and due to lack of available space it could have only one track.
在这里插入图片描述
The local tradition is that every train arriving from the direction A continues in the direction B with coaches reorganized in some way. Assume that the train arriving from the direction A has N <= 1000 coaches numbered in increasing order 1, 2, …, N. The chief for train reorganizations must know whether it is possible to marshal coaches continuing in the direction B so that their order will be a1, a2, …, aN. Help him and write a program that decides whether it is possible to get the required order of coaches. You can assume that single coaches can be disconnected from the train before they enter the station and that they can move themselves until they are on the track in the direction B. You can also suppose that at any time there can be located as many coaches as necessary in the station. But once a coach has entered the station it cannot return to the track in the direction A and also once it has left the station in the direction B it cannot return back to the station.
[Input]
The input consists of blocks of lines. Each block except the last describes one train and possibly more requirements for its reorganization. In the first line of the block there is the integer N described above. In each of the next lines of the block there is a permutation of 1, 2, …, N. The last line of the block contains just 0.
The last block consists of just one line containing 0.
[Output]
The output contains the lines corresponding to the lines with permutations in the input. A line of the output contains Yes if it is possible to marshal the coaches in the order required on the corresponding line of the input. Otherwise it contains No. In addition, there is one empty line after the lines corresponding to one block of the input. There is no line in the output corresponding to the last ``null’’ block of the input.
[Sample Input]
5
1 2 3 4 5
5 4 1 2 3
0
6
6 5 4 3 2 1
0
0
[Sample Output]
Yes
No
Yes

【算法设计】 首先入栈前的序列肯定时1-n的,所以先把序列预先存在a数组里,然后对于每个n有很多序列,判断这些序列合不合法,就要从这些序列的第一的开始看,1-n肯定是都要依次入栈的,只不过在此过程中,有的可能需要出栈,所以需要出栈的序列就是给出的那些序列,然后模拟一下,就行了.

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn=1e4+7;
int n;
struct Stack{
    int *data;
    int siz;
    int head,rear;
    bool init(int si){
        siz=si;
        data=new int[siz];
        head=rear=-1;
        if(!data)return false;
        return true;
    }
    bool sempty(){
        if(head==rear)return true;
        return false;
    }
    bool sfull(){
        if(head==siz)return true;
        return false;
    }
    bool push(int value){
        if(sfull())return false;
        data[++head]=value;
        return true;
    }
    bool pop(){
        if(sempty())return false;
        head--;
        return true;
    }
    int gettop(){
        if(!sempty())return data[head];
        return inf;
    }
}s;
int a[maxn],b[maxn];
int main(){
    for(int i=1;i<maxn;i++)a[i]=i;
    while(scanf("%d",&n),n){//n为0时输入结束
        while(scanf("%d",&b[1])){//特判b[1]是否为0
            if(b[1]==0)break;
            s.init(n);//栈初始化
            for(int i=2;i<=n;i++)scanf("%d",&b[i]);
            int tot=1;
            for(int i=1;i<=n;i++){
                s.push(a[i]);
                if(s.gettop()==b[tot]){
                    while(s.gettop()==b[tot]){
                        s.pop();tot++;
                    }
                }
                else continue;
            }
            if(tot==n+1)puts("Yes");//满足条件
            else puts("No");
        }
    }
    return 0;
}
6.迷宫求解

【题目描述】
以一个m*n的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计一个程序,对任意设定的迷宫,求出一条从入口到出口的通路,或得出没有通路的结论。
【算法设计】
采用试探法,从某一点出发,按照上下左右四个方向依次探测,直到到达终点为止。深度优先搜索采用回溯法,从每一个位置出发,下一步都有四种选择(上右下左),先选择一个方向,如果该方向能够走下去,那么就往这个方向走,当前位置切换为下一个位置。如果不能走,那么换个方向走,如果所有方向都走不了,那么退出当前位置,到上一步的位置去,当前位置切换为上一步的位置。一致这样执行下去,如果当前位置是终点,那么结束。如果走过了所有的路径都没能到达终点,那么无解。

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
const int N=1e5+7;
struct P{
    int x,y;
    P(){}
    P(int x,int y):x(x),y(y){}
};
struct sqQueue{//封装循环队列
    P *data;
    int siz;
    int head,rear;
    bool init(int si){//初始化
        siz=si;
        data=new P[siz];
        if(!data)return false;
        head=rear=0;
        return true;
    }
    bool qempty(){//判空
        if(head==rear)return true;
        return false;
    }
    bool qfull(){//判满
        if((rear+1)%siz==head)return true;
        return false;
    }
    int getsize(){//队列元素个数
        return (rear-head+siz)%siz;
    }
    bool en(P value){//进队
        if(qfull())return false;
        data[rear++]=value;
        rear%=siz;
        return true;
    }
    bool pop(){//出队
        if(qempty())return false;
        head=(head+1)%siz;
        return true;
    }
    P getfront(){//取队头元素
        return data[head];
    }
}q;
struct Stack{//封装栈
    P *v;
    int siz;
    int head,rear;
    bool init(int si){//初始化
        siz=si;
        v=new P[siz];
        head=rear=-1;
        if(!v)return false;
        return true;
    }
    bool sempty(){//判空
        if(head==rear)return true;
        return false;
    }
    bool sfull(){//判满
        if(head==siz)return true;
        return false;
    }
    bool push(P value){//进栈
        if(sfull())return false;
        v[++head]=value;
        return true;
    }
    bool pop(){//栈顶元素出栈
        if(sempty())return false;
        head--;
        return true;
    }
    P gettop(){//取栈顶元素
        return v[head];
    }
}s,w;
int n,m;
int mp[maxn][maxn],path[maxn][maxn];//0上1下2左3右
bool vis[maxn][maxn];
char str[5]={'D','U','R','L'};
int bx,by,ex,ey;
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
void print(int now,int x,int y){//递归打印路径
    if(x==bx&&y==by)return;
    for(int i=0;i<4;i++){
        if(now==i){
            int nx=x+dx[i],ny=y+dy[i];
            print(path[nx][ny],nx,ny);
            printf("%d %d %c\n",nx,ny,str[i]);
        }
    }
}
void bfs(){//广度优先搜索
    q.init(N);
    q.en(P(bx,by));
    vis[bx][by]=1;
    int flag=0;
    while(!q.qempty()){
        P now=q.getfront();q.pop();
        if(now.x==ex&&now.y==ey){flag=1;break;}
        for(int i=0;i<4;i++){//四个方向
            int nx=now.x+dx[i],ny=now.y+dy[i];
            if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&!vis[nx][ny]&&mp[nx][ny]!=1){
                vis[nx][ny]=1;
                if(i==0)path[nx][ny]=1;
                else if(i==1)path[nx][ny]=0;
                else if(i==2)path[nx][ny]=3;
                else path[nx][ny]=2;
                q.en(P(nx,ny));
            }
        }
    }
    if(!flag)puts("无通路");
    else print(path[ex][ey],ex,ey);
}
void dfs(){//深度优先搜索
    s.init(N);
    w.init(N);
    s.push(P(bx,by));
    w.push(P(bx,by));
    int flag=0;
    while(!s.sempty()){
        P now=s.gettop();s.pop();
        if(now.x==ex&&now.y==ey){flag=1;break;}
        for(int i=0;i<4;i++){
            int nx=now.x+dx[i],ny=now.y+dy[i];
            if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&!vis[nx][ny]&&mp[nx][ny]!=1){
                vis[nx][ny]=1;
                if(i==0)path[nx][ny]=1;
                else if(i==1)path[nx][ny]=0;
                else if(i==2)path[nx][ny]=3;
                else path[nx][ny]=2;
                s.push(P(nx,ny));
            }
        }
    }
    if(!flag)puts("无通路");
    else print(path[ex][ey],ex,ey);
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            vis[i][j]=0;
            cin>>mp[i][j];//0为通路、1为障碍、2为起点、3为终点
            if(mp[i][j]==2){
                bx=i,by=j;
            }
            if(mp[i][j]==3){
                ex=i,ey=j;
            }
        }
    }
    //bfs();
    dfs();
    return 0;
}

实验三.二叉树的基本操作及应用

1.二叉树的基本操作

【问题描述】
建立一棵二叉树,试编程实现二叉树的如下基本操作:

  1. 按先序序列构造一棵二叉链表表示的二叉树T;
  2. 对这棵二叉树进行遍历:先序、中序、后序以及层次遍历,分别输出结点的遍历序列;
  3. 求二叉树的深度/结点数目/叶结点数目;(选做)
  4. 将二叉树每个结点的左右子树交换位置。(选做)
    【基本要求】
    从键盘接受输入(先序),以二叉链表作为存储结构,建立二叉树(以先序来建立),
    【测试数据】
    如输入:ABCффDEфGффFффф(其中ф表示空格字符)
      则输出结果为
    先序:ABCDEGF
      中序:CBEGDFA
      后序:CGEFDBA
    层序:ABCDEFG
    【选作内容】
    采用非递归算法实现二叉树遍历。
#include <bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
int depth=0;
char n;
struct treenode{
    char data;
    struct treenode *l,*r;
};
void creat_tree(treenode *&T){//先序建树
    n=getchar();
    if(n==' ')T=NULL;
    else {
        if(!(T=(treenode *)malloc(sizeof(treenode))))exit(1);
        T->data=n;
        creat_tree(T->l);
        creat_tree(T->r);
    }
}
void level_search(treenode *root){//层次遍历
    treenode *p=root;
    queue<treenode*> q;
    q.push(p);
    while(!q.empty()){
        p=q.front();q.pop();
        if(p){
            printf("%c",p->data);
            treenode *k=p->l,*o=p->r;
            if(k)q.push(k);
            if(o)q.push(o);
        }
    }
}
void pre(treenode *root){//先序遍历递归写法
    if(root==NULL)return;
    printf("%c",root->data);
    pre(root->l);
    pre(root->r);
}
void pre1(treenode *root){//先序遍历非递归写法
    treenode *p=root;
    stack<treenode*> s;
    while(p||!s.empty()){
        if(p){
            printf("%c",p->data);
            s.push(p);
            p=p->l;
        }
        else {
            p=s.top();s.pop();
            p=p->r;
        }
    }
}
void in(treenode *root){//中序遍历递归写法
    if(root==NULL)return;
    in(root->l);
    printf("%c",root->data);
    in(root->r);
}
void in1(treenode *root){//中序遍历非递归写法
    treenode *p=root;
    stack<treenode*> s;
    while(p||!s.empty()){
        if(p){
            s.push(p);
            p=p->l;
        }
        else {
            p=s.top();s.pop();
            printf("%c",p->data);
            p=p->r;
        }
    }
}
void post(treenode *root){//后序遍历递归写法
    if(root==NULL)return;
    post(root->l);
    post(root->r);
    printf("%c",root->data);
}
void post1(treenode *root){//后序遍历非递归写法
    treenode *p=root,*r=NULL;
    stack<treenode*> s;
    while(p||!s.empty()){
        if(p){
            s.push(p);
            p=p->l;
        }
        else {
            p=s.top();
            if(p->r&&p->r!=r)p=p->r;
            else {
                s.pop();
                printf("%c",p->data);
                r=p;//记录最近访问过的节点
                p=NULL;//重置p指针
            }
        }
    }
}
void get_depth(treenode *node,int sum){//求根到叶子的最大深度
    if(depth<sum)depth=sum;
    if(node->l!=NULL)get_depth(node->l,sum+1);
    if(node->r!=NULL)get_depth(node->r,sum+1);
}

int main()
{
    treenode *Troot;
    creat_tree(Troot);
    pre(Troot);puts("");
    pre1(Troot);puts("");
    in(Troot);puts("");
    in1(Troot);puts("");
    post(Troot);puts("");
    post1(Troot);puts("");
    level_search(Troot);puts("");
    get_depth(Troot,0);cout<<depth<<endl;
    return 0;
}
2.哈夫曼编码

利用哈夫曼编码进行通信,可以压缩通信的数据量,提高传输效率,缩短信息的传输时间,还有一定的保密性。现在要求编写一程序模拟传输过程,实现在发送前将要发送的字符信息进行编码,然后进行发送,接收后将传来的数据进行译码,即将信息还原成发送前的字符信息。
现在有两个功能:
1 发送者:将待传送的字符信息转化为哈夫曼编码。
2 接受者:将接受的编码信息进行译码,得到还原成发送前的字符信息。
输入
输入一个小写字母组成的字符串S(strlen(S)<=1e3),表示待传送的字符串。
输出
输出为两行,第一行将输入的字符串转化为哈夫曼编码的最短长度。第二行输出哈夫曼编码译码的长度。
输入:
hailhydra
输出:
25
9

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
int str[30];
char s[maxn];
typedef struct
{
    char ch;//叶子结点所表示的字符
    int weight;//结点权值
    int lchild,rchild,parent;
}htnode;//树的结点
struct hufmtree{
    htnode *no;//哈夫曼树
    char **HC;//哈夫曼编码
    char *DHC;//哈夫曼译码
    int n,m;//n为要编码的不同字符个数
    void init_tree(int siz){//初始化
        n=siz;
        m=(n<<1)-1;
        no=new htnode[m+1];
        no[0].weight=maxn;
        for(int i=1;i<=m;i++){
            no[i].parent=no[i].lchild=no[i].rchild=no[i].weight=0;
            no[i].ch='1';
        }
    }
    void Select(int ma,int &s1,int &s2){//选择两个最小的
    	s1=0,s2=0;
        for(int i=1;i<=ma;i++){
            if(no[i].parent!=0)continue;
            if(no[i].weight<no[s1].weight)s1=i;
        }
        for(int i=1;i<=ma;i++){
            if(no[i].parent!=0||i==s1)continue;
            if(no[i].weight<no[s2].weight)s2=i;
        }
    }
    void creat_tree(){//建立哈夫曼树
        int tot=1;
        for(int i=0;i<26;i++){
            if(str[i]){
                no[tot].weight=str[i];
                no[tot++].ch=(char)('a'+i);
            }
        }
        for(int i=n+1;i<=m;i++){//合并n-1次
            int s1,s2;
            Select(i-1,s1,s2);
            no[s1].parent=i;no[s2].parent=i;
            no[i].lchild=s1;no[i].rchild=s2;
            no[i].weight=no[s1].weight+no[s2].weight;
        }
    }
    void creathufcode(){//编码
        HC=new char*[n+1];
        char *cd=new char[n];//辅助数组
        cd[n-1]='\0';
        for(int i=1;i<=n;i++){//每个字符编码后存入HC数组
            int st=n-1,c=i;
            int f=no[i].parent;
            while(f!=0){
                --st;
                if(no[f].lchild==c)cd[st]='0';
                else cd[st]='1';
                c=f;f=no[f].parent;
            }
            HC[i]=new char[n-st];
            strcpy(HC[i],&cd[st]);
        }
        delete cd;
    }
    void decode(char str[]){//译码
        int len=strlen(str);
        DHC=new char[maxn];
        int c=m,tot=0;
        for(int i=0;i<len;i++){//根据传入的字符串来译码
            if(str[i]=='0'){
                c=no[c].lchild;
            }
            else c=no[c].rchild;
            if(no[c].ch!='1'){
                DHC[tot++]=no[c].ch;
                c=m;
            }
        }
        DHC[tot]='\0';
    }
}HT;

int main(){
    cin>>s;
    int len=strlen(s);
    for(int i=0;i<len;i++){
        int now=s[i]-'a';
        str[now]++;
    }
    int tot=0;
    for(int i=0;i<26;i++){
        if(str[i]){
            tot++;
        }
    }
    HT.init_tree(tot);
    HT.creat_tree();
    HT.creathufcode();
    char ss[maxn<<4];
    tot=0;
    for(int i=0;i<len;i++){
        for(int j=1;j<=HT.n;j++){
            if(HT.no[j].ch==s[i]){
                int sum=strlen(HT.HC[j]);
                for(int k=0;k<sum;k++){
                    ss[tot++]=HT.HC[j][k];
                }
                break;
            }
        }
    }
    ss[tot]='\0';
    HT.decode(ss);
    int ans=0;
    for(int i=1;i<=HT.n;i++){
        int len=strlen(HT.HC[i]);
        ans+=len*HT.no[i].weight;
    }
    int sum=strlen(HT.DHC);
    cout<<ans<<'\n'<<sum<<'\n';
    return 0;
}
3.果子合并

【问题描述】
n堆果子, 每堆果子数量任意,试设计一种最佳方案,将这n堆果子合并为一堆,使得合并工作量最小。
注:规定合并两堆果子的工作量是这两堆果子的数量之和。
【标准输入】
M,N M表示M组测试数据,N表示每组测试数据数量不超过N个,每堆果子数量不超过10000。随后的M行是测试数据。
【标准输出】
M行数据表示对应果子的合并工作量
【输入样例】:
2 6
7 5 2 4
5 6 2 9 7
【输出样例】:
35
65

【算法设计】
根据原序列建一个小根堆,不断取两个最小的合并,将合并的贡献加入答案中,直到堆中只有一个根节点为止。
以第一个样例为例:

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
int n,m,t;
int heap[maxn];

void creat_tree(){//建堆
    for(int i=1;i<=n;i++){
        scanf("%d",&heap[i]);
        char s=getchar();
        if(s=='\n'){
            t=i;
            break;
        }
    }
}
void Minheapfixup(int i,int n){
    int temp,j;
    temp=heap[i];
    j=i*2;
    while(j<=n){
        if(j+1<=n&&heap[j+1]<heap[j])j++;
        if(heap[j]>=temp)break;
        heap[i]=heap[j];
        i=j;
        j=2*i;
    }
    heap[i]=temp;
}
void print(){//打印结果
    for(int i=n;i>=1;i--){//要逆序打印
        printf("%d%c",heap[i],i==1?'\n':' ');
    }
}
void get_minheap(){
    for(int i=n/2;i>=1;i--){//最小堆
        Minheapfixup(i,n);
    }
    for(int i=n;i>=1;i--){//最小堆排序
        swap(heap[i],heap[1]);
        Minheapfixup(1,i-1);
    }
}
int main()
{
    cin>>m>>n;
    while(m--){
        creat_tree();//建堆
        int q=t,tot=t,ans=0;
        while(--q){//一共合并t-1次
            get_minheap();//得到小根堆
            print();//输出查看
            heap[tot-1]+=heap[tot];//合并两个最小的
            ans+=heap[tot-1];//加入答案中
            heap[tot]=-1;tot--;
        }
        cout<<ans<<'\n';
    }
    return 0;
}
4.平衡二叉树的基本操作

【题目描述】
(1)用二叉链表作存储结构,以回车(’\n’)为输入结束标志,输入数列L,生成一棵平衡的二叉排序树T,并以直观的方式显示在终端上;
(2)对二叉排序树T作中序遍历,输出结果;
(3)输入元素x,查找二叉排序树T,若存在含x的结点,则删除该结点,并作中序遍历(执行操作2);否则输出信息“无x”,并将x插入该二叉排序树中。
注意:插入、删除应保证二叉排序树的平衡性。
【算法思想】
平衡二叉排序树,要求对于任意一个结点,其左子树上的所有结点的值均小于它的根节点的值,其右子树上所有结点的值均大于它根节点的值。且任意一个结点的左右儿子的高度之差不超过1。
对于四种调整方式:
(1)LL失去平衡的情况,可以通过一次旋转让AVL树恢复平衡。
在这里插入图片描述
LL旋转是围绕"失去平衡的AVL根节点"进行的,也就是节点k2;而且由于是LL情况,即左左情况,就用手抓着"左孩子,即k1"使劲摇。将k1变成根节点,k2变成k1的右子树,“k1的右子树"变成"k2的左子树”。
(2)RR是与LL对称的情况
在这里插入图片描述
(3)LR失去平衡的情况,需要经过两次旋转才能让AVL树恢复平衡。
在这里插入图片描述
第一次旋转是围绕"k1"进行的"RR旋转",第二次是围绕"k3"进行的"LL旋转"
(4)RL是与LR的对称情况
在这里插入图片描述
所以对于平衡二叉树的创建,需要按照输入次序,依次插入到二叉树指定位置。如果在插入某一个元素时导致树不平衡就判断并做相应调整。
对于单一的插入操作,从根节点开始依次与待插入元素比较,确定插入的位置,如果发现当前结点与待插入元素相同,则返回失败信息,否则在空缺处插入元素,插入后判断如果树不平衡就调整。
对于查询操作,从根节点出发,比较键值确定待查找元素位置,直到找到或没找到,返回成功或失败信息。
对于删除操作,先查找,如果找不到就不需要删除,则返回失败信息,若找到,则执行删除函数,找到之后需要判断其有几个儿子,如果没有儿子,则直接删除即可,如果只有一个儿子,则直接让它替换掉待删除结点即可,如果有两个儿子,则有两个选择,一个是让右孩子的最小孩子替换它,二是让左孩子的最大孩子替换它,先判断两边子树哪边高,用高的那边的去替换然后删除,这样即使删除了,树也依然平衡,就不用调整了,对于其他情况,在删除之后需要判断下是否平衡,不平衡就调整。
对于树的销毁,可以从根进行按照后序遍历的顺序删除存在的各结点。

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
const int N=1e5+7;

int flag;
typedef struct node{
    int w,height;
    struct node *lchild,*rchild;
}AVLnode;
/*
3 2 1 4 5 6 7 16 15 14 13 12 11 10 8 9 -1
*/
struct BalanceTree{
    AVLnode *root=NULL;//根
    int node_height(AVLnode *q){
        return q==NULL?0:q->height;
    }
    AVLnode* create_node(int x,AVLnode *lchild,AVLnode *rchild){//创建AVLTree结点
        AVLnode *p=new AVLnode;
        p->w=x;
        p->height=0;
        p->lchild=lchild;
        p->rchild=rchild;
        return p;
    }
    AVLnode* ll_rotation(AVLnode *q){//LL型
        AVLnode *p;
        p=q->lchild;
        q->lchild=p->rchild;
        p->rchild=q;
        q->height=max(node_height(q->lchild),node_height(q->rchild))+1;
        p->height=max(node_height(p->lchild),q->height)+1;
        return p;//返回旋转后的根结点
    }
    AVLnode* rr_rotation(AVLnode *q){//RR型
        AVLnode *p;
        p=q->rchild;
        q->rchild=p->lchild;
        p->lchild=q;
        q->height=max(node_height(q->lchild),node_height(q->rchild))+1;
        p->height=max(node_height(p->rchild),q->height)+1;
        return p;//返回旋转后的根结点
    }
    AVLnode* lr_rotation(AVLnode *q){//LR型
        q->lchild=rr_rotation(q->lchild);
        return ll_rotation(q);
    }
    AVLnode* rl_rotation(AVLnode *q){//RL型
        q->rchild=ll_rotation(q->rchild);
        return rr_rotation(q);
    }
    AVLnode* lmin_node(AVLnode *q){//查找最左结点
        if(!q)return NULL;
        while(q->lchild)q=q->lchild;
        return q;//返回最左结点
    }
    AVLnode* rmin_node(AVLnode *q){//查找最右结点
        if(!q)return NULL;
        while(q->rchild)q=q->rchild;
        return q;//返回最右结点
    }
    AVLnode* insert_node(AVLnode *q,int x){//插入
        if(!q){//新建节点表示插入
            q=create_node(x,NULL,NULL);
        }
        else if(q->w>x){//应插到左子树
            q->lchild=insert_node(q->lchild,x);
            //插入后失衡需要调整
            if(node_height(q->lchild)-node_height(q->rchild)==2){
                if(q->lchild->w>x)q=ll_rotation(q);
                else q=lr_rotation(q);
            }
        }
        else if(q->w<x){//应插到右子树
            q->rchild=insert_node(q->rchild,x);
            //插入后失衡需要调整
            if(node_height(q->rchild)-node_height(q->lchild)==2){
                if(q->rchild->w<x)q=rr_rotation(q);
                else q=rl_rotation(q);
            }
        }
        else {//存在相同键值
            flag=1;
        }
        q->height=max(node_height(q->lchild),node_height(q->rchild))+1;
        return q;
    }
    bool create_tree(){//创建AVL树
        int x;
        flag=0;
        while(1){
            cin>>x;
            if(x==-1)break;
            root=insert_node(root,x);
        }
        if(!flag)return true;
        return false;
    }
    AVLnode* search_node(AVLnode *q,int x){//查找
        if(!q||q->w==x)return q;
        if(q->w>x)return search_node(q->lchild,x);
        else return search_node(q->rchild,x);
    }
    AVLnode* delete_node(AVLnode *q,AVLnode *z){//删除
        if(!q||!z)return NULL;//根为空或要删除的结点不存在
        if(q->w>z->w){//待删除结点在左子树
            q->lchild=delete_node(q->lchild,z);
            //删除后失衡要调整
            if(node_height(q->rchild)-node_height(q->lchild)==2){
                AVLnode *r=q->rchild;
                if(node_height(r->lchild)>node_height(r->rchild)){
                    q=rl_rotation(q);
                }
                else q=rr_rotation(q);
            }
        }
        else if(q->w<z->w){//待删除结点在右子树
            q->rchild=delete_node(q->rchild,z);
            //删除后失衡要调整
            if(node_height(q->lchild)-node_height(q->rchild)==2){
                AVLnode *r=q->lchild;
                if(node_height(r->rchild)>node_height(r->lchild)){
                    q=lr_rotation(q);
                }
                else q=ll_rotation(q);
            }
        }
        else {//要删除的结点
            if(q->lchild&&q->rchild){//左右儿子均存在
                if(node_height(q->lchild)>node_height(q->rchild)){
                    // 如果tree的左子树比右子树高;
                    // 则(01)找出tree的左子树中的最大节点
                    //   (02)将该最大节点的值赋值给tree。
                    //   (03)删除该最大节点。
                    // 这类似于用"tree的左子树中最大节点"做"tree"的替身;
                    // 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。
                    AVLnode *m=rmin_node(q->lchild);
                    q->w=m->w;
                    q->lchild=delete_node(q->lchild,m);
                }
                else {
                    // 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)
                    // 则(01)找出tree的右子树中的最小节点
                    //   (02)将该最小节点的值赋值给tree。
                    //   (03)删除该最小节点。
                    // 这类似于用"tree的右子树中最小节点"做"tree"的替身;
                    // 采用这种方式的好处是:删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。
                    AVLnode *m=lmin_node(q->rchild);
                    q->w=m->w;
                    q->rchild=delete_node(q->rchild,m);
                }
            }
            else {//只有一个儿子
                AVLnode *tmp=q;
                q=q->lchild?q->lchild:q->rchild;
                delete tmp;
            }
        }
        return q;
    }
    AVLnode* avl_delete_node(AVLnode *q,int x){//根据键值删除
        AVLnode *z;
        z=search_node(q,x);
        if(z)q=delete_node(q,z);
        else flag=1;
        return q;
    }
    void pro(AVLnode *q){//前序遍历
        if(q){
            printf("%d ",q->w);
            pro(q->lchild);
            pro(q->rchild);
        }
    }
    void mid(AVLnode *q){//中序遍历
        if(q){
            mid(q->lchild);
            printf("%d ",q->w);
            mid(q->rchild);
        }
    }
    void pos(AVLnode *q){//后序遍历
        if(q){
            pos(q->lchild);
            pos(q->rchild);
            printf("%d ",q->w);
        }
    }
    void destroy_tree(AVLnode *q){//销毁平衡二叉树
        if(!q)return;
        destroy_tree(q->lchild);
        destroy_tree(q->rchild);
        delete q;
    }
    void print_info(AVLnode *q,int fa,int role){
        if(q){
            if(role==0)printf("%2d is root.\n",q->w);//role:0为根结点
            else if(role==-1)printf("%2d is %2d's lchild.\n",q->w,fa);//role:1为左孩子结点
            else printf("%2d is %2d's rchild.\n",q->w,fa);//role:2为右孩子结点
            print_info(q->lchild,q->w,-1);
            print_info(q->rchild,q->w,1);
        }
    }
}AVL;
void menu(){
    puts("\n1.建立平衡二叉树");
    puts("2.插入元素");
    puts("3.删除元素");
    puts("4.查找元素");
    puts("5.前序遍历平衡二叉树");
    puts("6.中序遍历平衡二叉树");
    puts("7.后序遍历平衡二叉树");
    puts("8.打印AVL树信息");
    puts("9.销毁平衡二叉树");
    puts("10.结束\n");
}
int main(){
    while(1){
        menu();
        int op,x;
        cin>>op;
        if(op==1){
            puts("请输入序列:");
            if(AVL.create_tree())puts("创建成功!");
            else puts("创建失败!");
        }
        else if(op==2){
            puts("请输入要插入的结点权值:");
            cin>>x;
            flag=0;
            AVL.root=AVL.insert_node(AVL.root,x);
            if(!flag)puts("插入成功!");
            else puts("插入失败!");
        }
        else if(op==3){
            puts("请输入要删除的结点权值:");
            cin>>x;
            flag=0;
            AVL.root=AVL.avl_delete_node(AVL.root,x);
            if(!flag){
                puts("删除成功!");
            }
            else puts("删除失败!");
        }
        else if(op==4){
            puts("请输入要查找的结点权值:");
            cin>>x;
            if(AVL.search_node(AVL.root,x))puts("存在该结点!");
            else puts("不存在该结点!");
        }
        else if(op==5){
            if(!AVL.root)puts("空");
            else {
                AVL.pro(AVL.root);puts("");
            }
        }
        else if(op==6){
            if(!AVL.root)puts("空");
            else {
                AVL.mid(AVL.root);puts("");
            }
        }
        else if(op==7){
            if(!AVL.root)puts("空");
            else {
                AVL.pos(AVL.root);puts("");
            }
        }
        else if(op==8){
            AVL.print_info(AVL.root,AVL.root->w,0);
        }
        else if(op==9){
            AVL.destroy_tree(AVL.root);
            AVL.root=NULL;
            puts("销毁成功!");
        }
        else break;
    }
    return 0;
}

实验四.图的基本操作及应用

1.图的遍历

【问题描述】
  对给定图,实现图的深度优先遍历和广度优先遍历。
【基本要求】
   以邻接表为存储结构,实现连通无向图的深度优先和广度优先遍历。以用户指定的结点为起点,分别输出每种遍历下的结点访问序列。
【测试数据】
  由学生依据软件工程的测试技术自己确定。

#include <bits/stdc++.h>

using namespace std;

const int V=1e3;

int vis[V];    //访问标志的数组
typedef struct ArcNode     //边表结点
{
    int adjvex;     //邻接点域,存储该顶点对应的下标
    struct ArcNode *next;     //链域,指向下一个邻接点
}ArcNode;

typedef struct VNode   //顶点表结点
{
    int id;    //顶点表结点
    ArcNode *firstarc;   //边表头指针
}VNode,AdjList[V];

typedef struct
{
    AdjList vertices;
    int vexnum,arcnum;    //图中当前顶点数和边数
}ALGraph;

void creat(ALGraph &G){
    cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数
    for(int i=1;i<=G.vexnum;i++){
        G.vertices[i].id=i;   //输入顶点值
        G.vertices[i].firstarc=NULL; //初始化表头结点的指针域为NULL
    }
    for(int i=0;i<G.arcnum;i++){
        int u,v;
        cin>>u>>v;
        u=G.vertices[u].id;
        v=G.vertices[v].id;
        ArcNode *p1=new ArcNode;
        ArcNode *p2=new ArcNode;
        p1->adjvex=v;
        p1->next=G.vertices[u].firstarc;G.vertices[u].firstarc=p1;
        p2->adjvex=u;
        p2->next=G.vertices[v].firstarc;G.vertices[v].firstarc=p2;
    }
}
//邻接表的深度优先递归算法
void dfs(ALGraph G,int i)
{
    ArcNode *p;
    vis[i]=true;
    printf("%d ",G.vertices[i].id);   //打印顶点,也可以是其他操作
    p=G.vertices[i].firstarc;
    while(p)
    {
        if(!vis[p->adjvex])
            dfs(G,p->adjvex);  //对未访问的邻接点递归调用
        p=p->next;
    }
}
//邻接表的深度遍历操作
void DFSTraverse(ALGraph G)
{
    for(int i=1;i<=G.vexnum;i++)
        vis[i]=false;   //初始化所有的顶点状态都是未访问过状态
    for(int i=1;i<=G.vexnum;i++){
        if(!vis[i]){ //对未访问过的顶点调用dfs,若是连通图,则只执行一次
            dfs(G,i);
        }
    }
}
//邻接表的广度优先递归算法
void bfs(ALGraph G,int i){
    queue<int> q;
    while(!q.empty())q.pop();
    q.push(G.vertices[i].id);
    vis[G.vertices[i].id]=1;
    while(!q.empty()){
        int v=q.front();q.pop();
        printf("%d ",G.vertices[v].id);
        ArcNode *p=G.vertices[v].firstarc;
        while(p){
            if(!vis[p->adjvex]){
                vis[p->adjvex]=1;
                q.push(p->adjvex);  //未访问的邻接点进队
            }
            p=p->next;
        }
    }
}
//邻接表的广度遍历操作
void BFSTraverse(ALGraph G){
    for(int i=1;i<=G.vexnum;i++)
        vis[i]=false;   //初始化所有的顶点状态都是未访问过状态
    for(int i=1;i<=G.vexnum;i++){
        if(!vis[i]){ //对未访问过的顶点调用bfs,若是连通图,则只执行一次
            bfs(G,i);
        }
    }
}
int main()
{
    int t;
    cin>>t;
    ALGraph G;
    for(int i=1;i<=t;i++){
        creat(G);
        DFSTraverse(G);puts("");
        BFSTraverse(G);puts("");
    }
    return 0;
}
2.最小生成树问题

【问题描述】
若要在n个城市之间建设通信网络,只需要假设n-1条线路即可。如何以最低的经济代价建设这个通信网,是一个网的最小生成树问题。
【基本要求】
1.利用克鲁斯卡尔算法求网的最小生成树。
2.要求输出各条边及它们的权值。
【实现提示】
通信线路一旦建成,必然是双向的。因此,构造最小生成树的网一定是无向网。设图的顶点数不超过30个,并为简单起见,网中边的权值设成小于100的整数。

【算法设计】
基于贪心思想,每次选择非最小生成树的最小的边加入到最小生成树的集合中,知道n的顶点联通,所以要对所有的边从小到大排个序,然后贪心的从小到大选,每次加之前判断加入后是否会产生环,可以用并查集简单的实现,知道生成最小生成树。

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
int n,m;
int f[50];
struct node{//边
    int from,to,cost;
    node(){}
    node(int from,int to,int cost):from(from),to(to),cost(cost){}
}no[maxn];
vector<node> v;//存最小生成树的边

int fin(int x)//找x节点的祖先
{
    return f[x]==x?x:f[x]=fin(f[x]);
}
void unite(int x,int y)//将x,y加入最小生成树顶点集合
{
    x=fin(x);
    y=fin(y);
    if(x!=y)f[x]=y;
}
bool same(int x,int y)//判断x,y是否在一个集合里
{
    return fin(x)==fin(y);
}

bool cmp(node x,node y)//将边的权值从小到大排个序
{
    return x.cost<y.cost;
}

void kruskal()
{
    sort(no,no+m,cmp);
    int sum=0;//最小生成树总权值
    for(int i=0;i<m;i++)
    {
        node a=no[i];
        if(!same(a.from,a.to)){//贪心的加边操作
            unite(a.from,a.to);
            sum+=a.cost;
            v.push_back(a);
        }
    }
    printf("最小生成树总权值为:%d\n",sum);
    for(int i=0;i<(int)v.size();i++){//输出最小生成树的所有边
        node now=v[i];
        printf("%d<->%d %d\n",now.from,now.to,now.cost);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)//输入顶点个数与边的个数
    {
        v.clear();
        for(int i=1;i<=n;i++)f[i]=i;
        for(int i=0;i<m;i++){
            cin>>no[i].from>>no[i].to>>no[i].cost;
        }
        kruskal();
    }
    return 0;
}
3.拓扑排序的应用

【题目描述】
试编程,为某校计算机科学与技术专业的四年全部课程做一个排课方案。假设课程号从1开始,且所有课程号不重复,每个课程号对应一门课。
【标准输入】
n m 其中n表示n门课程,m表示m对课程关系,比如x y表示要想修课程编号为y的课程要先修课程编号为x的课程 。
第二行n个字符串,第i个表示课程编号为i的课程名。
接下来m行,每行两个数字,用空格隔开,表示两个课程编号。
【标准输出】
输出任意一种可行的排课方案,只输出课程编号,相邻两个课程编号用空格隔开
【输入样例】
3 2
C Java DataStructure
1 2
1 3
5 6
A B C D E
1 2
1 3
2 3
3 4
3 5
5 4
【输出样例】
其中一种可行的排课方案为:1 3 2
其中一种可行的排课方案为:1 2 3 5 4

【算法思想】
选课规则是要想修某门课,必须先修掉它所有的先修课,所以它与它先修课就存在一种前后对应的关系,依次建图,课表示成点,课之间的关系表示成边,那么每门课会与其先修课有一条单向边,由先修课指向它,所以最开始先修入度为0的课,然后将这些课删除,这些删除的点所指向的点的入度都相应减1,再依次选入读为0的点,循环执行,知道所有的课都被删除。
我们按第二组样例:
5 6
A B C D E
1 2
1 3
2 3
3 4
3 5
5 4
则具体过程如图一所示:

在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
int n,m;
int deg[maxn]; //存每个顶点的入度
char str[maxn][50];//各课程对应的课程编号
int ans[maxn],tot;//存一种可行的排课方案序列
struct sqQueue{//队列存入度为0的点
    int *data;
    int siz;
    int head,rear;
    bool init(int si){//初始化
        siz=si;
        data=new int[siz];
        if(!data)return false;
        head=rear=0;
        return true;
    }
    bool qempty(){//判空
        if(head==rear)return true;
        return false;
    }
    bool qfull(){//判满
        if((rear+1)%siz==head)return true;
        return false;
    }
    int getsize(){//队列元素个数
        return (rear-head+siz)%siz;
    }
    bool en(int value){//进队
        if(qfull())return false;
        data[rear++]=value;
        rear%=siz;
        return true;
    }
    bool pop(){//出队
        if(qempty())return false;
        head=(head+1)%siz;
        return true;
    }
    int getfront(){//取队头元素
        return data[head];
    }
}q;
typedef struct ArcNode     //边表结点
{
    int adjvex;     //邻接点域,存储该顶点对应的下标
    struct ArcNode *next;     //链域,指向下一个邻接点
}ArcNode;

typedef struct VNode   //顶点表结点
{
    int sum;    //顶点表结点
    ArcNode *firstarc;   //边表头指针
}VNode,AdjList[maxn];

typedef struct ALGraph
{
    AdjList vertices;
    int vexnum,arcnum;    //图中当前顶点数和边数
}ALG;

void init(ALG &G,int N,int M){
    G.vexnum=N;
    G.arcnum=M;
    for(int i=1;i<=G.vexnum;i++){
        G.vertices[i].sum=0; //存顶点i相邻的点的个数
        G.vertices[i].firstarc=NULL; //初始化表头结点的指针域为NULL
    }
}
void addarc(ALG &G,int u,int v){
    ArcNode *p1=new ArcNode;
    p1->adjvex=v;
    p1->next=G.vertices[u].firstarc;G.vertices[u].firstarc=p1;
    G.vertices[u].sum++;
}
ALG G;
int main()
{
    while(cin>>n>>m)//n门课,m个对应关系
    {
        q.init(maxn);
        init(G,n,m);
        tot=0;
        for(int i=1;i<=n;i++){
            cin>>str[i];
            deg[i]=0;//初始化所有边入度为零
        }
        for(int i=0;i<m;i++){
            int u,v;
            cin>>u>>v;
            addarc(G,u,v);//建图
            deg[v]++;//v顶点入度加一
        }
        for(int i=1;i<=n;i++){
            if(!deg[i]){//入度为0的顶点进队
                q.en(i);
            }
        }
        while(!q.qempty()){//还存在入度为零的点
            int u=q.getfront();q.pop();
            ans[tot++]=u;
            ArcNode *p=G.vertices[u].firstarc;
            while(p){
                int v=p->adjvex;
                deg[v]--;
                if(!deg[v]){//入度减一后如果顶点入度为0就进队
                    q.en(v);
                }
                p=p->next;
            }
        }
        if(tot<n){
            puts("没有可行的排课方案");
        }
        else {
            printf("其中一种可行的排课方案为:");
            for(int i=0;i<tot;i++){
                printf("%d%c",ans[i],i==tot-1?'\n':' ');
            }
        }
    }
    return 0;
}
4.最短路径问题

【问题描述】
  给定一个无向网,可以求得任意一对顶点之间的最短路径。
【基本要求】
  以邻接矩阵为存储结构,实现弗洛伊德算法求解每一对顶点之间的最短路径及最短路径长度。

(1)floyed算法

【算法思想】
基于动态规划的思想,枚举所有的点对的距离,并找一个点作为中间点,不断进行松弛操作,从而确定所有点对间的最短路,即所有点最短路的前驱。

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e3+10;
const int inf=0x3f3f3f3f;
int n,m;
int d[maxn][maxn],g[maxn][maxn];//最短路图,原图邻接矩阵
int path[maxn][maxn];//存前驱
void init(){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=inf;
            if(i==j)g[i][j]=0;
        }
    }
}
void floyed(){//floyed算法具体实现
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            d[i][j]=g[i][j];
            if(d[i][j]!=inf&&i!=j)path[i][j]=i;
            else path[i][j]=-1;
        }
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(d[i][j]>d[i][k]+d[k][j]){//以k作为中间点进行松弛操作
                    d[i][j]=d[i][k]+d[k][j];
                    path[i][j]=path[k][j];
                }
            }
        }
    }
}
void print(){//打印结果
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(d[i][j]==inf||i==j){
                printf("顶点%d<-->%d之间不存在路\n",i,j);
            }
            else {
                printf("顶点%d<-->%d之间最短距离为:%d\n",i,j,d[i][j]);
                printf("顶点%d的前驱为:%d\n",i,path[i][j]);
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)//n个顶点,m条边
    {
        init();
        for(int i=0;i<m;i++){
            int u,v,w;
            cin>>u>>v>>w;
            g[u][v]=d[v][u]=w;
        }
        floyed();
        print();
    }
    return 0;
}
(2)dijkstra算法

dijkstra算法是针对单源最短距离的算法,未优化的时间复杂度为: O ( V 2 ) O(V^2) O(V2),用堆优化后复杂度为: O ( E l o g 2 V ) O(Elog_2V) O(Elog2V)。已是求单源最短路中的一种较为优秀的算法,但其有缺点,不能处理有负边的图。

【步骤】
a.初始时,只包括源点,即S = {v},v的距离为0。U包含除v以外的其他顶点,即:U ={其余顶点},若v与U中顶点u有边,则(u,v)为正常权值,若u不是v的出边邻接点,则(u,v)权值 ∞;
b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
d.重复步骤b和c直到所有顶点都包含在S中。

#include <bits/stdc++.h>

using namespace std;
typedef pair<int,int> P;
const int maxn=1e3+10;
const int inf=0x3f3f3f3f;
int n,m;
int d[maxn];
vector<P> G[maxn];//first存点,second存距离
void dijkstra(int s,int t){//求s,t的单源最短距离
    memset(d,inf,sizeof(d));//初始化为无穷大
    d[s]=0;
    priority_queue<P,vector<P>,greater<P> > pq;//first存到s最短距离,second存顶点
    pq.push(P(0,s));
    while(!pq.empty()){
        P p=pq.top();pq.pop();
        int u=p.second;
        if(p.first>d[u])continue;//如果当前点的距离不是最小就跳过
        for(int i=0;i<(int)G[u].size();i++){
            P now=G[u][i];
            int v=now.first;
            if(d[v]>d[u]+now.second){//存在距离更短的路就更新
                d[v]=d[u]+now.second;
                pq.push(P(d[v],v));
            }
        }
    }
    if(d[t]==inf)printf("顶点%d<-->%d不存在最短路\n",s,t);
    else printf("顶点%d<-->%d的最短距离为:%d\n",s,t,d[t]);
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
        for(int i=1;i<=n;i++)G[i].clear();
        for(int i=0;i<m;i++){
            int u,v,w;
            cin>>u>>v>>w;
            G[u].push_back(P(v,w));//邻接表建无向图
            G[v].push_back(P(u,w));
        }
        int q;
        cin>>q;
        while(q){//求任意给定两点的最短路
            int u,v;
            cin>>u>>v;
            dijkstra(u,v);
        }
    }
    return 0;
}

实验五.查找和排序的应用

1.学生信息管理系统

【基本要求】
设计一个学生信息管理系统,学生对象至少要包含:学号、姓名、性别、成绩1、成绩2、总成绩等信息。要求实现以下功能:
1.试选择一种方式存储:基于数组、链表或文件方式
2.总成绩要求自动计算;
3.查询:分别给定学生学号、姓名,能够查找到学生的基本信息(要求至少用两种查找算法实现);
排序:分别按学生的学号、总成绩进行排序(要求至少用两种排序算法实现)。

1)希尔排序
该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的。不妨假设步长从n/2开始,每次减半,直到为1为止,再对整体进行一次直接插入排序。
2)归并排序
该方法的基本思路是:先递归分解数列,再合并数列。将数组分成二组,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。所以可以将这两组各自再分成二组。依次类推进行递归,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。将二个有序数列合并,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
3)快速排序
该方法的基本思路是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
4)折半查找
该方法的基本思路是:搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组已经为空,则表示找不到指定的元素。
5)散列查找
该方法的基本思路是:以数据(通常为集合)中的每个元素的关键字key为自变量,通过一种函数H(key)计算出函数值,把这个值解释为一块连续存储空间(即数组空间或文件空间)的单元地址(即下标),将该元素存储到这个单元中。查找时也是根据这个H(key)来查找其在散列表中的位置。

#include <bits/stdc++.h>

using namespace std;

int n,m;
struct stu{
    string id,name,sex;
    int math,english,sum;
    stu(){}
    stu(string id,string name,string sex,int math,int english,int sum):
    id(id),name(name),sex(sex),math(math),english(english),sum(sum){}
};
typedef struct{
    int key=0;
    stu data;
}hashtable;
int H(string x){
    int len=x.length(),sum=0;
    for(int i=0;i<len;i++){
        sum=(sum+x[i]-'0')%n;
    }
    return sum;
}
struct StudentManagementSystem{
    stu *st;
    hashtable *ht;
    int siz,tot;
    void init(int si){//初始化
        tot=0;
        siz=si;
        st=new stu[si];
        ht=new hashtable[si];
    }
    void insert_data(string id,string name,string sex,int math,int english){//插入数据
        int sum=math+english;
        st[tot++]=stu(id,name,sex,math,english,sum);
        int key=H(id);
        if(!ht[key].key){//建立hash表
            ht[key].key=key;
            ht[key].data=st[tot-1];
        }
        else {
            for(int i=1;i<siz;i++){
                int hi=(key+i)%siz;
                if(!ht[hi].key){
                     ht[hi].key=key;
                     ht[hi].data=st[tot-1];
                     break;
                }
            }
        }
    }
    void print(){//打印结果
        for(int i=0;i<tot;i++){
            cout<<st[i].id<<' '<<st[i].name<<' '<<st[i].sex<<' '<<st[i].math<<' '<<st[i].english<<' '<<st[i].sum<<'\n';
        }
    }
    void Shellsort(){//希尔排序
        for(int gap=tot>>1;gap>0;gap>>=1){//分的组数
            for(int i=gap;i<tot;i++){
                for(int j=i-gap;j>=0&&st[j].id>st[j+gap].id;j-=gap){
                    swap(st[j],st[j+gap]);
                }
            }
        }
    }
    void mergearray(int l,int mid,int r,stu temp[]){//将两个有序数列合并
        int i=l,j=mid+1;
        int m=mid,n=r,k=0;
        while(i<=m&&j<=n){//两半边合并
            if(st[i].id<st[j].id){
                temp[k++]=st[i++];
            }
            else temp[k++]=st[j++];
        }
        while(i<=m)temp[k++]=st[i++];//左边还有元素就直接加到末尾
        while(j<=n)temp[k++]=st[j++];//右边一样
        for(i=0;i<k;i++){
            st[l+i]=temp[i];
        }
    }
    void mergesort(int l,int r,stu temp[]){//递归分解数列
        if(l<r){
            int mid=(l+r)>>1;
            mergesort(l,mid,temp);//左边有序
            mergesort(mid+1,r,temp);//右边有序
            mergearray(l,mid,r,temp);//将两个有序表合并
        }
    }
    void Mergesort(){//归并排序
        stu *ss=new stu[tot];//辅助数组
        mergesort(0,tot-1,ss);
        delete[] ss;
    }
    void Quicksort(int l,int r){//快排
        if(l<r){
            int i=l,j=r;stu x=st[l];
            while(i<j){
                while(i<j&&st[j].id>=x.id)j--;
                if(i<j)st[i++]=st[j];
                while(i<j&&st[i].id<x.id)i++;
                if(i<j)st[j--]=st[i];
            }
            st[i]=x;
            Quicksort(l,i-1);
            Quicksort(i+1,r);
        }
    }
    stu BinarySearch(int l,int r,string x,int op){//折半查找
        stu ans=stu("NULL","NULL","NULL",-1,-1,-1);
        if(op==1){//按id二分查询
            while(l<=r){
                int m=(l+r)>>1;
                if(st[m].id>x)r=m-1;
                else if(st[m].id<x)l=m+1;
                else return st[m];
            }
        }
        else {//按name二分查询
            while(l<=r){
                int m=(l+r)>>1;
                if(st[m].name>x)r=m-1;
                else if(st[m].name<x)l=m+1;
                else return st[m];
            }
        }
        return ans;
    }
    stu HashSearch(string x){//散列查找
        stu ans=stu("NULL","NULL","NULL",-1,-1,-1);
        int h0=H(x);
        if(!ht[h0].key)return ans;
        else if(ht[h0].data.id==x)return ht[h0].data;
        else {
            for(int i=1;i<siz;i++){
                int hi=(h0+i)%siz;
                if(!ht[hi].key)return ans;
                else if(ht[hi].data.id==x)return ht[hi].data;
            }
            return ans;
        }
    }
}SMS;
int main()
{
    while(cin>>n){
        SMS.init(n);
        for(int i=0;i<n;i++){
            string id,name,sex;
            int math,english;
            cin>>id>>name>>sex>>math>>english;
            SMS.insert_data(id,name,sex,math,english);
        }
        //SMS.Mergesort();
        //SMS.Shellsort();
        SMS.Quicksort(0,n-1);
        SMS.print();
        cin>>m;
        string op,x;
        while(m--){
            cin>>op>>x;
            if(op=="id"){
               //stu ans=SMS.BinarySearch(0,n-1,x,1);//传1查id
                stu ans=SMS.HashSearch(x);
                if(ans.id=="NULL")puts("没找到");
                else cout<<ans.id<<' '<<ans.name<<' '<<ans.sex<<' '<<ans.math<<' '<<ans.english<<' '<<ans.sum<<'\n';
            }
            else {//折半查找前需要先按name字典序排序
                stu ans=SMS.BinarySearch(0,n-1,x,2);//传2查name
                if(ans.id=="NULL")puts("没找到");
                else cout<<ans.id<<' '<<ans.name<<' '<<ans.sex<<' '<<ans.math<<' '<<ans.english<<' '<<ans.sum<<'\n';
            }
        }
    }
    return 0;
}
/*
5
66 ll man 88 100
88 44 man 10 0
04 ww woman 77 77
12 nn man 50 75
06 wa man 80 60
5
id 04
id 88
id 89
id 12
id 06
*/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值