MENU

数据结构实习大作业

December 22, 2020 • Read: 45 • 默认分类

数据结构课程设计

首先恭喜你已经通过明亮的眼睛找到了这篇博客的密码,这篇文章是给那些“忙于学习”的同学写的。所以,废话不多说,直接上代码。

一、报数问题

问题描述

有n个小朋友围成一圈玩游戏,小朋友从1至n编号,2号小朋友坐在1号小朋友的顺时针方向,3号小朋友坐在2号小朋友的顺时针方向,……,1号小朋友坐在n号小朋友的顺时针方向。
 游戏开始,从1号小朋友开始顺时针报数,接下来每个小朋友的报数是上一个小朋友报的数加1。若一个小朋友报的数为k的倍数或其末位数(即数的个位)为k,则该小朋友被淘汰出局,不再参加以后的报数。当游戏中只剩下一个小朋友时,该小朋友获胜。
 例如,当n=5, k=2时:
 1号小朋友报数1;
 2号小朋友报数2淘汰;
 3号小朋友报数3;
 4号小朋友报数4淘汰;
 5号小朋友报数5;
 1号小朋友报数6淘汰;
 3号小朋友报数7;
 5号小朋友报数8淘汰;
 3号小朋友获胜。
 给定n和k,请问最后获胜的小朋友编号为多少?

输入格式

  输入一行,包括两个整数n和k,意义如题目所述。

输出格式

  输出一行,包含一个整数,表示获胜的小朋友编号。

样例输入

5 2

样例输出

3

样例输入

7 3

样例输出

4

数据规模和约定

对于所有评测用例,1 ≤ n ≤ 1000,1 ≤ k ≤ 9。
要求:利用单向循环链表存储结构模拟此过程。

代码部分

#include<iostream>
using namespace std;
#include<stdlib.h>
//这题是模拟过程可抽象为在每个时间片状态模拟。
//像是动画制作,每一帧代表一片时间,每一帧的图案对应状态,帧与帧的转换等价于状态改变
typedef struct person{
 int index;//个人属性:序号+存活状态
 struct person *next;
}Person;

typedef Person* Ptr;//方便同时定义多个结构体指针

Ptr create_single_circle_list(int n)//创建单向循环链表,构造初始状态及个体间的关系
{
 //Person* head;Person* pre;Person* last;
 //结构体指针必须分开定义,或者把Person* 定义成Ptr
 Ptr head,pre,last;//头、后继、前驱指针

 head =(Ptr)malloc(sizeof(Person));
 head->index=1;
 pre=head;
 for(int i=1;i<n;i++)
 {
     Person* last=(Ptr)malloc(sizeof(Person));
     last->index=i+1;
     pre->next=last;
     pre=last;
 }
 pre->next=head;
 return head;
}

Ptr play(Ptr head,int n,int k)//报数过程模拟
{
 Ptr pre,last;
 int count=1;//模拟报号数字
 pre=head;
 while(n>1)//全局属性:n为目前还存活的人数
 {
     //index既代表每个人固定的序号,
     //也代表着当前个人存活状态:正数=>存活; -1=>死亡
     //只有活着的人可以报数,所以只对存活者操作
     if(pre->index!=-1)
     {
         //若当前存活者报的数为k的倍数或个位数为k,
         //当前状态变为死亡
         //当前存活人数减一
         //计算或是模拟过程无非是状态的转变,此题的结果判断依赖于当前存活人数,
         //分解为个人存活、全局存活人数两个状态
         if(count%k==0||count%10==k)
         {
             pre->index=-1;
             n--;
         }

         count++;//只要当前状态为存活,就必须报数
     }
     pre=pre->next;//无论当前存活状态如何都必须模拟报数人的移动
 }

 return head;
}

void output(Ptr head,int n)//输出获胜编号
{
 Ptr pre=head;
 while(n>1)
 {
     if(pre->index!=-1)
     {
         cout<<pre->index;
         break;
     }
     else pre=pre->next;
 }
}

int main()
{
 int n,k;
 cin>>n>>k;
 Ptr head=create_single_circle_list(n);
 head=play(head,n,k);
 output(head,n);
 return 0;
}

二、迷宫问题求解

问题描述

任务

​ 可以输入一个任意大小的迷宫数据,用非递归的方法求出一条走出迷宫的路径,并将路径输出;

要求

在上交资料中请写明:存储结构、基本算法(可以使用程序流程图)、源程序、测试数据和结果、算法的时间复杂度、另外可以提出算法的改进方法;

代码部分

#include <bits/stdc++.h>
using namespace std;
#define MAXSIZE 1000
typedef int Status;
typedef struct
{
    int x;
    int y;
} Postype;
typedef struct
{
    int ord;      //通道块路径上的序号
    Postype seat; //通道快在迷宫中的坐标位置
    int dir;      //从此通道快走向下一个通道快的方向
} SElemType;      //栈的元素类型
typedef struct
{
    //SElemType data[MAXSIZE];
    SElemType *top;
    SElemType *base;
} Stack; //栈的结构类型
typedef struct
{
    char arr[MAXSIZE][MAXSIZE];
} MAZETYPE; //迷宫结构体

MAZETYPE maze;
//创建一个迷宫图
void InitMaze(int m, int n)
{
    printf("请开始您的私人地图\n");
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> maze.arr[i][j];
        }
    }
    printf("你建立的迷宫为(最外圈为墙)...\n");
    for (int i = 0; i <= m + 1; i++) //加一圈围墙
    {
        maze.arr[i][0] =  '1';
        maze.arr[i][n + 1] = '1';
    }
    for (int j = 0; j <= n + 1; j++)
    {
        maze.arr[0][j] = '1';
        maze.arr[m + 1][j] = '1';
    }
    for (int i = 0; i <= m + 1; i++) //输出迷宫
    {
        for (int j = 0; j <= n; j++)
        {
            printf("%c ", maze.arr[i][j]);
        }
        printf("%c", maze.arr[i][n + 1]);
        printf("\n");
    }
}
//创建一个栈初始化
Status initStack(Stack &s)
{
    s.base = (SElemType *)malloc(MAXSIZE * sizeof(SElemType));
    if (!s.base)
        return 0;
    s.top = s.base;
    return 1;
}
//入栈
void Push(Stack &s, SElemType e)
{
    *s.top++ = e;
}
//出栈
void Pop(Stack &s, SElemType &e)
{
    e = *--s.top;
}
//判栈空
Status StackEmpty(Stack &s)
{
    if (s.top == s.base)
        return 1;
    else
        return 0;
}
//可通过
Status Pass(Postype curpos)
{
    if (maze.arr[curpos.x][curpos.y] == '0')
        return 1;
    else
        return 0;
}
//留下通过的足迹
void Foot(Postype curpos)
{
    maze.arr[curpos.x][curpos.y] = '*';
}
//标记不可通的位置
void MarkPrint(Postype curpos)
{
    maze.arr[curpos.x][curpos.y] =  '!';
}
//判断是否已经到达了出口
Status StructCmp(Postype a, Postype b)
{
    if (a.x == b.x && a.y == b.y)
        return 1;
    else
        return 0;
}
//寻找下一个位置
Postype NextPos(Postype CurPos, int Dir)
{
    Postype ReturnPos;
    switch (Dir)
    {
    case 1: //探索右
        ReturnPos.x = CurPos.x;
        ReturnPos.y = CurPos.y + 1;

        break;
    case 2: //下
        ReturnPos.x = CurPos.x + 1;
        ReturnPos.y = CurPos.y;

        break;
    case 3: //左
        ReturnPos.x = CurPos.x;
        ReturnPos.y = CurPos.y - 1;

        break;
    case 4: //上
        ReturnPos.x = CurPos.x - 1;
        ReturnPos.y = CurPos.y;
        break;
    default:
        break;
    }
    return ReturnPos;
}
//进行路径的查找
Status MazePath(Postype start, Postype end1, Stack &s1)
{
    /*
        如果迷宫maze中存在从入口start到出口end的通道,
        则求得一条存放在栈中(从栈底到栈顶),并返回1,否则返回0。
    */
    SElemType e;
    Postype curpos = start;
    int curstep = 1;
    do
    {
        if (Pass(curpos))
        {
            Foot(curpos);              //留下足迹
            e = {curstep, curpos, 1}; //将序列号,可通点的坐标和存入
            Push(s1, e);              //将其存入战中
            if (StructCmp(curpos, end1))
            {
                maze.arr[end1.x][end1.y] =  '@';
                return 1;
            }                             //若找到出口则输出
            curpos = NextPos(curpos, 1); //查找下一个位置
            curstep++;
        }
        else
        {
            if (!StackEmpty(s1))
            {
                SElemType e;
                Pop(s1, e);
                curstep--;
                while (e.dir == 4 && !StackEmpty(s1))
                {
                    MarkPrint(e.seat);
                    Pop(s1, e);
                    curstep--; //若此位置的上下左右都不通,则标记并在栈中删除此位置
                }
                if (e.dir < 4) //&& !StackEmpty(s))//与上面相对应,则继续查找此位置的下一个可通位置
                {
                    e.dir++;
                    curpos = NextPos(e.seat, e.dir);
                    curstep++;
                    Push(s1, e);
                }
            }
        }
    } while (!StackEmpty(s1));
    return 0;
}
void mg(Postype s,Postype e,Stack s1,int m,int n)
{
    SElemType p;
    if (MazePath(s, e, s1))
    {
        printf("it is sucessful!\n");
        cout << "-=================================" << endl;
        for (int i = 0; i <= m + 1; i++)
        {
            for (int j = 0; j <= n + 1; j++)
            {
                printf("%c ", maze.arr[i][j]);
            }
            printf("\n");
        }
        while (!StackEmpty(s1))
        {
            Pop(s1, p);
            printf("(%d,%d)>>", p.seat.x, p.seat.y);
        }
    }
    else
        printf("it's a error\n");
}
int main()
{
    int m, n,l;
    Postype s, e;
    Stack s1;
    initStack(s1);
    cout << "请输入这个迷宫图的行号和列号:" << endl;
    cin >> m >> n;
    InitMaze(m, n);
    cout << "请输入这个迷宫图的入口和出口:" << endl;
    cin >> s.x >> s.y >> e.x >> e.y;
    mg(s,e,s1,m,n);
    /*if (MazePath(s, e, s1))
    {
        printf("it is sucessful!\n");
        cout << "-=================================" << endl;
        for (int i = 0; i <= m + 1; i++)
        {
            for (int j = 0; j <= n + 1; j++)
            {
                printf("%c ", maze.arr[i][j]);
            }
            printf("\n");
        }
        while (!StackEmpty(s1))
        {
            Pop(s1, p);
            printf("(%d,%d)>>", p.seat.x, p.seat.y);
        }
    }
    else
        printf("it's a error\n");
    */
    return 0;
}
/*
输入:
8 8
    0 1 0 1 0 0 1 0
    0 0 1 0 0 1 0 1
    0 0 0 1 1 0 1 0
    0 1 0 0 0 1 0 1
    0 1 0 0 0 1 1 1
    0 1 0 0 1 0 1 0
    1 0 0 0 0 0 0 1
    0 1 0 0 0 1 0 0
输出:
1 1
8 8
    (8,8)>>(8,7)>>(7,7)>>(7,6)>>(7,5)>>(7,4)>>(6,4)>>(5,4)>>(5,5)>>(4,5)>>(4,4)>>(4,3)>>(3,3)>>(3,2)>>(2,2)>>(2,1)>>(1,1)>>
*/

三、哈夫曼编/译码器

1、问题描述

任务

建立最优二叉树函数。

要求

可以建立函数输入二叉树,并输出其哈夫曼树。

在上交资料中请写明:存储结构、基本算法(可以使用程序流程图)、输入输出、源程序、测试数据和结果、算法的时间复杂度、另外可以提出算法的改进方法;

利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼码的编/译码系统。

一个完整的系统应具有以下功能:

  1. I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中。
  2. E:编码(Encoding)。利用已建好的哈夫曼树(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
  3. D:译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
  4. P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。
  5. T:印哈夫曼树(Tree printing)。将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。

2、代码部分

#include <iostream>
#include <fstream>
#include <cstring>
#define MAXSIZE 100
#include<bits/stdc++.h>
using namespace std;

//哈夫曼树的存储结构
typedef struct HaffmanTree{
    double weight;                    //权值
    char letter;
    int parent,lChild,rChild;    //结点的双亲、左孩子、右孩子的下标
}HTNode,*HuffmanTree;            //态分配数组储存哈夫曼树

typedef char **HuffmanCode;        //动态分配数组储存哈夫曼编码表

//栈储存结构
typedef struct{
    int *base;        //栈底指针
    int *top;        //栈顶指针
    int stackSize;    //栈的最大容量
}SqStack;

//栈初始化
int InitStack (SqStack &S){
    S.base = new int[MAXSIZE];  //为顺序栈动态分配空间
    if(!S.base)
        //分配失败
        exit(0);
    S.top = S.base;
    S.stackSize = MAXSIZE;
    return 1;
}

//入栈
int Push(SqStack &S,int elem){
    if(S.top-S.base == S.stackSize)        //栈满
        return 0;
    *S.top++ = elem;
    return 1;
}

//出栈
int Pop(SqStack &S,int &elem){
    if(S.top == S.base)                //栈空
        return 0;
    elem = *--S.top;                //取出栈顶元素并出栈
    return 1;
}

//判断是否为空栈
int StackEmpty(SqStack &S){
    if(S.top == S.base)        //栈空,返回1
        return 1;
    else
        return 0;
}

//寻找最小的两个元素           s1<s2
void Select(HuffmanTree &HT,int n,int &s1,int &s2){
    int i =1 ,j;
    for(i;i < n && HT[i].parent != 0;++i);          //找到第一个双亲为0的点
    j = i;
    for(i = i+1;i < n && HT[i].parent != 0;++i);    //找到第二个双亲为0的点
    if(HT[i].weight < HT[j].weight){                  //使s1<s2
        s1 = i;s2 = j;
    }
    else{
        s1 = j;s2 = i;
    }
    while(++i <= n){                                //找权值最小的两个点
        if(HT[i].parent == 0){
            if(HT[i].weight < HT[s1].weight){
                s2 = s1;
                s1 = i;
            }else if(HT[i].weight < HT[s2].weight)
                s2 = i;
        }
    }
}

//建立哈夫曼树
void CreatTree(HuffmanTree &HT,int n) {
    int m;
    if(n <= 1){
        printf("字符个数不足!\n");
        return;
    }
    m = 2*n-1;
    HT = new HTNode[m+1];        //从1开始,0未用,需要动态分配 m+1 个单元,HT[m]表示根节点
    for(int i = 1;i <= m;i++){  //将双亲、左孩子、右孩子的下标初始化为0
        HT[i].parent = 0;
        HT[i].lChild = 0;
        HT[i].rChild = 0;
    }

    /*------------初始化n个字符及其权值----------- */
    for(int i = 1;i <= n;i++) {
        cout << "请输入第" << i << "个字符及权值:";
        cin >> HT[i].letter;
        cin >> HT[i].weight;
        while(!cin){
            cin.clear();        //更改cin的状态标示符
            cin.sync();            //清除缓存区的数据流
            cout << "格式错误, 重新输入\n";
            cin >> HT[i].letter;
            cin >> HT[i].weight;
        }
    }
    ofstream fout("hfmTree.txt");
    for(int i = 1;i <= n;i++){
        fout << HT[i].letter << HT[i].weight;
        fout << endl;
    }

    /*------------创建哈夫曼树----------- */
    int s1,s2;
    for(int i = n+1;i <= m;i++){    //通过n-1次的选择、删除、合并来创建哈夫曼树
        Select(HT,i-1,s1,s2);        //找出权值最小的两个
        HT[s1].parent = i;
        HT[s2].parent = i;            //将双亲设为i

        HT[i].lChild = s1;
        HT[i].rChild = s2;          //将其作为左右孩子
        HT[i].weight = HT[s1].weight + HT[s2].weight;    //双亲的权值为左右孩子权值之和
    }
}

//初始字符编码
void Code(HuffmanTree HT,HuffmanCode &HC,int n) {
    /*-------------------字初始符编码-----------------------------------*/
    int start,child,parent;
    HC = new char*[n+1];                 //分配n个字符编码的编码表空间
    char* cd = new char[n];              //分配临时存放每个字符编码的动态数组空间
    cd[n-1] = '\0';                     //编码结束符
    for(int i = 1;i <= n;i++){
        start = n-1;                    //start开始指向最后,即编码结束符的位置
        child = i;
        parent = HT[i].parent;            //parent指向节点child的双亲节点
        while(parent != 0){
            --start;                    //回溯一次start向前指一个位置
            if(HT[parent].lChild == child)
                cd[start] = '0';        //child为parent的左孩子,生成0
            else
                cd[start] = '1';        //child为parent的右孩子,生成1
            child = parent;
            parent = HT[parent].parent;    //继续向上回溯
        }
        HC[i] = new char[n-start];        //为第i个字符编码分配空间
        strcpy(HC[i],&cd[start]);         //将求得的编码从临时空间cd复制到HC的行列中
    }
    delete cd;                            //释放临时空间
    cout << "对应字符编码为:\n";
    for(int i = 1;i <= n;i++){
        cout << HT[i].letter << ":" << HC[i] << endl;
    }
    /*-------------------------将字符编码写入文件---------------------------------*/
    ofstream fout("CodeFile.txt");
    for(int i = 1;i <= n;i++){
        fout <<  HC[i];
    }
}

//对正文编码
void TextCode(HuffmanTree HT,HuffmanCode &HC,int n){
    ifstream file;
    file.open("code_text.txt");
    if(!file){                    //判断文件是否打开
        cout << "打开code_text.txt失败!\n";
        exit(0);
    }

    ofstream fout("code_result.txt");
    int flag;
    char ch;
    while(file.get(ch)){
        for(int i = 1;i <= n;i++){
            if(ch == HT[i].letter){
                fout << HC[i];
                flag = 1;        //标志flag为1,匹配到初始化的字符
                break;
            }
            else
                flag = 0;
        }
        if(!flag)
            fout << ch;
    }
    cout << "编码完成\n"
         << "已初始化的字符由编码代替\n"
         << "未初始化的字符不进行替换\n";
}

//译码
void Decode(HuffmanTree HT,int n) {
    ifstream file;
    file.open("CodeFile.txt");
    if(!file){                    //判断文件是否打开
        cout << "打开CodeFile1.txt失败!\n";
        exit(0);
    }

    ofstream fout("TextFile.txt");
    int m = 2*n-1;; //根节点
    char ch;
    while(file.get(ch)){
        if(ch == '1')
            m = HT[m].rChild;
        else if(ch == '0')
            m = HT[m].lChild;
        if(HT[m].lChild == 0 && HT[m].rChild == 0){  //当前字段解码完毕
            fout << HT[m].letter;
            m = 2*n-1;
        }
    }
    cout << "译码完成\n";
}

//显示哈夫曼树
void DisplayTree(HuffmanTree HT,int n) {
    SqStack S;
    InitStack(S);
    int temp,k;
    int m = 2*n-1;                    //根节点
    ofstream fout("TreePrint.txt");
    while(m != 0 || StackEmpty(S) == 0){
        while(m != 0){
            Push(S,m);                 //入栈
            m = HT[m].rChild;         //遍历右子树
        }
        if(!StackEmpty(S)){
            Pop(S,m);                //出栈

            k = 0;temp = m;            //计算离根节点距离,美化输出
            while(temp){
                temp = HT[temp].parent;
                k++;
            }
            for(int i = 1;i < k;i++){
                printf("    ");
            }
            fout<<HT[m].weight<<" ";
            cout << HT[m].weight << endl;
            m = HT[m].lChild; //遍历左子树
        }
    }
}

//显示正文的编码及译码结果
void Result(HuffmanTree HT){
    char ch;
    ifstream fileCode,fileDecode;

    /*-----------正文编码结果-------------------*/
    /*fileCode.open("code_result.txt");
    if(!fileCode){                    //判断文件是否打开
        cout << "打开code_result.txt失败!\n";
        exit(0);
    }
    cout << "正文编码结果:\n";
    while(fileCode.get(ch)){
        cout << ch;
    }
    cout << endl;*/

    /*-----------正文译码结果-------------------*/
    fileDecode.open("CodeFile.txt");
    if(!fileDecode){
        cout << "打开CodeFile.txt失败!\n";
        exit(0);
    }
    cout << "\n译码结果:\n";
    ofstream fout("CodePrin.txt");
    while(fileDecode.get(ch)){
        fout<<ch;
        cout << ch;
    }
    cout << endl << endl;
}

//菜单
void menu() {
    cout << "                    哈夫曼编/译码器\n";
    cout << "-------------------------------------------------------------------------------\n";
    cout << "                1 建立哈夫曼树\n";
    cout << "                2 字符编码\n";
    cout << "                3 译码\n";
    cout << "                4 显示哈夫曼树\n";
    cout << "                5 印代码文件\n";
    cout << "                0 退出程序\n";
    cout << "-------------------------------------------------------------------------------\n";
}

int main(int argc, char** argv) {
    int choice,n;
    HuffmanTree Tree;
    HuffmanCode HC;
    while(1){
        menu();
        cout << "\n请选择对应的功能选项进行相应操作:\n";
        cin >> choice;
        switch(choice){
            //建立哈夫曼树
            case 1:
                cout << "输入字符的个数:";
                cin >> n;
                while(!cin){
                    cin.clear();
                    cin.sync();            //清空流
                    cout << "格式错误, 重新输入\n";
                    cin >> n;
                }
                CreatTree(Tree,n);

                break;

            //编码
            case 2:
                if(Tree != NULL)
                    Code(Tree,HC,n);
                else
                    cout << "还未创建哈夫曼树\n";
                break;

            //译码
            case 3:
                if(Tree != NULL)
                    Decode(Tree,n);
                else
                    cout << "还未创建哈夫曼树\n";
                break;

            //显示哈夫曼树
            case 4:
                if(Tree != NULL)
                    DisplayTree(Tree,n);
                else
                    cout << "还未创建哈夫曼树\n";
                break;
            case 5:
                if(Tree != NULL)
                    Result(Tree);
                else
                    cout << "还未创建哈夫曼树\n";
                break;

            case 0:
                exit(0);

            default:
                cout << "请输入正确的操作序号!\n";
                break;
        }
        system("pause");
        system("cls");
    }
    return 0;
}

四、拓扑排序

1、问题描述

任务

编写函数实现图的拓扑排序。

2、代码部分

#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define MVNum 100
typedef int Status;
typedef char VerTexType;
typedef char OtherInfo;
int indegree[MVNum] = {0};
//创建栈
typedef struct StackNode{
    int data;
    StackNode *next;
}StackNode,*StackList;
//出栈函数
StackList Pop(StackList S, int *e)
{
    StackList p;
    p = S;
    if (!p)
        return ERROR;
    *e = p->data;
    S = S->next;
    free(p);
    return S;
}
//入栈函数:
StackList Push(StackList S,int e)
{
    StackList p;
    p = (StackNode *)malloc(sizeof(StackNode));
    p->data = e;
    p->next = S;
    S = p;
    return S;
}
//邻接表创建有向图的实现
//边结点
typedef struct ArcNode{    //链表结点
    int adjvex;           //邻接表创建无向网的实现
    ArcNode *nextarc;    //指向下一条边的指针
    OtherInfo info;       //和边相关的信息
}ArcNode;
//顶点信息
typedef struct VNode{   //头结点
    VerTexType data;   //顶点信息
    ArcNode *firstarc;//指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];//AdjList 表示邻接表类型

typedef struct{
    AdjList vertices;     //邻接表头结点数组
    int vexnum, arcnum;   //图的顶点数和弧数
}ALGraph;
//创建有向图:
//G带操作的图;v要在图中定位的顶点
int LocateVex(ALGraph *G, VerTexType v)
{
    int i;
    for (i = 0; i < (G->vexnum); i++)
    {
        if (v == G->vertices[i].data)
            return i;               //顶点存在则返回在头结点数组中的下标;否则返回
    }
}

void CreateUDG(ALGraph *G)
{
    int i, j, k;
    VerTexType v1, v2;
    ArcNode *p1;
    printf("Enter the total number of nodes and arcs:"); //G带操作的图;v要在图中定位的顶点
    scanf("%d %d", &G->vexnum, &G->arcnum);
    fflush(stdin);    //是清空输入缓冲区的
    printf("Enter values for each node:");
    for(i=0; i<G->vexnum;i++)   //邻接表初始化
    {
            scanf("%c", &G->vertices[i].data);
            G->vertices[i].firstarc = NULL;
    }
    for (k = 0; k < G->arcnum; k++)
    {
        fflush(stdin);   //是清空输入缓冲区的
        printf("Two points and two nodes of radian:");
        scanf("%c %c", &v1, &v2);
        i = LocateVex(G, v1);   //返回这两个顶点在顶点数组中的位置
        j = LocateVex(G, v2);
        p1 = (ArcNode *)malloc(sizeof(ArcNode));   //给邻接表指针分配空间
        p1->adjvex = j;                          //赋值给p->adjvex指向的顶点域
        p1->nextarc = G->vertices[i].firstarc; //nextarc指针域指向i结点的firstarc指针域
        G->vertices[i].firstarc = p1;    //将点i的第一条指针指向
        indegree[j]++;       //vi->vj, vj入度加1
    }
}
//拓扑排序算法
Status TopologicalSort(ALGraph G, int *topo)
{ //先声明栈指针S,并让其指向NULL。检查所有节点中是否有入度为0的节点,如果有,则进栈。
    int i, m, k;
    StackList S;  //先声明栈指针S,并让其指向NULL。
    ArcNode *p;
    S = NULL;
    for (i = 0; i < G.vexnum; i++) //检查所有节点中是否有入度为0的节点,如果有则进栈。
    {
        if (!indegree[i])  //当数组不为零时
            S=Push(S, i);
    }    //入度为零去完后
    m = 0; //记录topu数组的数
    while (S)//栈不为空的时候,先出栈,取出栈顶元素,并将其记录在topo[]数组中
    {
        S=Pop(S, &i);
        topo[m] = i;
        ++m;
        p = G.vertices[i].firstarc;   //指针p 指向第一条边的节点
        while (p != NULL)
        {
            k = p->adjvex;
            --indegree[k];
            if (indegree[k] == 0)
                S=Push(S, k);
            p = p->nextarc;
        }
    }
    topo[m] = -1;  // 为-1时结束
    if (m < G.vexnum)  // topo[]数组中元素是否已经达到节点数,
        return ERROR;
    else
        return OK;
}

int main(void)
{
    ALGraph G;
    int i;
    int topo[99] = {0};
    CreateUDG(&G);
    if (TopologicalSort(G, topo))
    {  printf("您输入的为有向图,无环\n拓扑排序序列为:");
        for (i = 0; topo[i] != -1; i++)
        {
            printf("%c ", G.vertices[topo[i]].data);
        }
    }
    else
        printf("您输入的为有环,请重新输入");
    printf("\n");
    return 0;
}
/*
    6 8
    123456
    1 2
    1 3
    1 4
    4 5
    6 5
    6 4
    3 2
    3 5
    shuchu:
        有向图,无环:
         序列为6 1 3 2 4 5
*/

五、散列文件的插入、删除和查找

1、问题描述

功能要求:
  1. 初始化三列文件;
  2. 向散列文件中插入一个元素;
  3. 从散列文件中删除一个元素;
  4. 从散列文件中查找一个元素。

散列文件通常采用链接法处理冲突。

散列文件中每个节点的类型定义为:

Struct  FLNode 
{ //散列主文件中的节点类型
    ElemType  data ; //值域
    Int  next; //指向下一个节点的指针域
};

2、代码描述

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
using namespace std;
/*定义散列表长度,其值取决于待三列的元素数量
和选取的装填因α的大小,在此假定为10*/
#define N 13
/*定义关键字类型,这里假定为整型*/
typedef int KeyType;
struct ElemType     //元素类型
{
    KeyType key;    //关键字域
    char rest[10];  //其他域,假定用字符数组表示
};
struct FLNode   //索引文件中的结点类型
{
    struct ElemType data;   //值域
    int next;       //指向下一个结点的指针域
};
const int b1 = sizeof(KeyType); //用全局常量b1保存索引表文件中的记录(元素)长度
const int b2 = sizeof(struct FLNode);   //用全局常量b2保存索引主文件中的记录(结点)长度

//定义散列主文件和索引文件的名字
char filename[]="Hash"; // 散列主文件名
char Iname[]="HashIndex"; // 索引文件名

//建立并初始化散列表文件
void InitHashFile(char *fname)
{
    // 确认
    char ch;
    printf("确实要重新初始化散列文件(y/n)?");
    getchar();
    scanf("%c", &ch);
    if('y' == ch || 'Y' == ch)
    {
        printf("--- 重新初始化散列文件完毕! ---\n");
    }
    else
    {
        printf("--- 未执行重建散列文件操作! ---\n");
        return;
    }

    /*------确认完成,开始初始化------*/
    int i;
    int *A;
    //以读写方式新建二进制散列文件
    FILE *fp, *fp2;
    fp = fopen(fname, "wb+");
    fp2 = fopen(Iname, "wb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("打开文件失败!/n");
        exit(1);
    }
    //动态分配具有N+1个整型储存空间的数组A
    A = (int *)calloc(N + 1, b1);
    if(NULL == A)
    {
        printf("内存申请失败!/n");
        exit(1);
    }

    //给数组A中的每个元素赋初值-1,表示空指针
    for(i = 0; i < N + 1; i++)
    {
        A[i] = -1;
    }
    //初始化散列文件
    fwrite((char *)A, (N + 1)*b1, 1, fp);
    fwrite((char *)A, (N + 1)*b1, 1, fp2);
    //删除数组A
    free(A);
    //关闭fp对应的文件
    fclose(fp);
    fclose(fp2);
}

//检测是否存在散列文件
void existHash(char *fname)
{
    char bcreate;
    char filename[128];
    FILE *fp, *fp2;
    fp = fopen(fname, "rb");
    fp2 = fopen(Iname, "rb");
    if(NULL == fp|| NULL == fp2)
    {
        printf("--- 散列文件不存在, 是否新建散列文件(y:重新建立;/n:打开其他散列文件)? ---\n");
        scanf("%c", &bcreate);
        if('y' == bcreate || 'Y' == bcreate)
        {
            InitHashFile(fname);
            printf("--- 新建散列文件完毕! ---\n");
        }
        else
        {
            printf("请输入散列文件路径:\n");
            scanf("%s", filename);
            strcpy(fname, filename);
            existHash(fname);
        }
    }
    else
    {
        fclose(fp);
        fclose(fp2);
    }
}

//把元素x插入到散列文件中
void HFInsertOne(char *fname, struct ElemType x)
{
    int p;
    int len;    //文件尾结点位置序号
    int *A;
    int d;
    struct FLNode temp;
    struct FLNode pn;
    //以读写和不新建方式打开散列文件
    FILE *fp, *fp2;
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp)
    {
        printf("打开文件失败!\n");
        exit(1);
    }
    //动态分配具有N + 1个整型存储空间的数组
    A = (int *)calloc(N + 1, b1);
    if(!A)
    {
        printf("内存申请失败!\n");
        exit(1);
    }
    //将索引文件的表头读入到数组A中
    fread((char *)A, (N + 1) * b1, 1, fp2);
    //以关键字x.key计算x的散列地址,采用除留余数法
    d = x.key % N;
    //以x和A[d]的值构成待插入散列文件的内存节点temp
    temp.data = x;
    temp.next = A[d];
    //将temp结点的值写入到散列文件中,并链接到散列文件表头
    //下表d单链表的表头
    if(-1 == A[N])
    {
        //将文件指针移至文件尾
        fseek(fp, 0L, 2);
        //计算出文件尾的结点位置序号
        len = (ftell(fp) - (N+1)*b1)/b2;
        //将temp结点的值写入文件尾
        fwrite((char *)&temp, b2, 1, fp);
        //使A[d]指向新插入的结点
        A[d] = len;
    }
    else
    {
        //p指向空闲单链表的表头结点
        p = A[N];
        //使空闲单链表的表头指针指向其下一个结点
        fseek(fp, b1 * (N+1) + p*b2, 0);
        fread((char *)&pn, b2, 1, fp);
        A[N] = pn.next;
        //使temp的值写入到p位置的结点上
        fseek(fp, -b2, 1);
        fwrite((char *)&temp, b2, 1, fp);
        //使A[p]指向新插入的p结点
        A[d] = p;
    }
    //将数组A中的全部内容写回到索引文件的表头中
    fseek(fp,0L,0);
    fseek(fp2,0L,0);
    fwrite((char *)A, b1 * (N+1), 1, fp2);
    //删除动态数组A和关闭散列文件
    free(A);
    fclose(fp);
    fclose(fp2);
}

//从散列文件中删除关键字尾x.key的元素,并由x带回该
//元素,若删除成功则返回1,否则返回0
int HFDelete(char *fname, struct ElemType *x)
{
    struct FLNode tp,tq;
    int p, q;
    int *A;
    int d;
    FILE *fp, *fp2;
    //打开散列文件
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("打开文件失败!\n");
        exit(1);
    }
    //申请动态数组A
    A = (int *)calloc(N+1, b1);
    if(NULL == A)
    {
        printf("内存申请失败!\n");
        exit(1);
    }
    //将索引文件表头读入数组A中
    fread((char *)A, (N+1)*b1, 1, fp2);

    //计算散列地址d
    d = x->key % N;
    p = A[d];
    while(-1 != p)
    {
        fseek(fp, (N+1)*b1+p*b2, 0);
        fread((char*)&tp, b2, 1, fp);
        if(tp.data.key  == x->key)
        {
            //被删除结点的元素值赋给x带回
            *x = tp.data;
            //从单链表中删除p结点
            if(p == A[d])
            {
                A[d] = tp.next;
            }
            else
            {
                tq.next = tp.next;
                fseek(fp, (N+1)*b1+q*b2, 0);
                fwrite((char *)&tq, b2, 1, fp);
            }
            //将p结点连接到空闲单链表的表头
            tp.next = A[N];
            fseek(fp, (N+1)*b1+p*b2, 0);
            fwrite((char *)&tp, b2, 1,fp);
            A[N] = p;
            //结束while循环
            break;
        }
        else
        {
            //使p指针的值赋给q,tp结点的值赋值给tq结点
            q = p;
            tq = tp;
            //p指向单链表中的下一个结点
            p = tp.next;
        }//if分支结束
    }//while循环结束
    //将索引文件的表头重新写入索引文件
    fseek(fp, 0L, 0);
    fseek(fp2, 0L, 0);
    fwrite((char *)A, (N + 1) * b1, 1, fp2);
    //释放A数组申请的内存空间
    free(A);
    //关闭散列文件
    fclose(fp);
    fclose(fp2);
    if(-1 == p)
    {
        return 0;   //没有找到要删除的结点
    }
    else
    {
        return 1;   //成功删除要删除的结点
    }//<end if>
}

//从散列文件中查找关键字为x.key的元素,并由x带回
//元素,若查找成功则返回1,否则返回0
int HFSearch(char *fname, struct ElemType *x)
{
    int d;
    int p;
    int *A;
    struct FLNode temp;
    //以读写方式打开散列文件
    FILE *fp, *fp2;
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("打开文件失败!\n");
        exit(1);
    }
    //申请动态数组A
    A = (int *)calloc(N+1, b1);
    if(NULL == A)
    {
        printf("内存申请失败!\n");
        exit(1);
    }
    fread((char *)A, (N+1)*b1, 1, fp2);
    d = x->key % N;
    //取出地址为d的单链表的表头指针(指向该地址的第一个存储元素)
    p = A[d];
    //从d点链表中查找关键字为x.key的元素
    while(p != -1)
    {
        fseek(fp, (N+1)*b1 + p*b2, 0);//在文件中定位
        fread((char *)&temp, b2, 1, fp);
        if(temp.data.key == x->key)
        {
            *x = temp.data; //被查找到的元素由x带回
            break;
        }
        else
        {
            p = temp.next;  //把结点指针移到下一个结点
        }
    }
    //释放A数组申请的内存空间
    free(A);
    //关闭文件
    fclose(fp);
    fclose(fp2);
    if(-1 == p)
    {
        return 0;
    }
    else
    {
        return 1;
    }//if分支结构结束
}
//顺序打印出散列文件中的每个单链表中的每个结点位置序号及元素值
void HFPrint(char *fname)
{
    int i;
    int p;
    int *A;
    struct FLNode pn;
    //以读写方式打开散列文件
    FILE *fp, *fp2;
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("打开文件失败!\n");
        exit(1);
    }

    //申请动态数组A
    A = (int *)calloc(N+1, b1);
    if(NULL == A)
    {
        printf("内存申请失败!\n");
        exit(1);
    }
    fread((char *)A, b1, N+1, fp2);
    for(i = 0; i < N+1; i++)
    {
        printf("%d:", i);
        p = A[i];
        while(-1 != p)
        {
            fseek(fp, (N+1)*b1 + p*b2, 0);  // 修改文件指针位置
            fread((char *)&pn, b2, 1, fp);  // 从文件中中读取节点
            printf("%d->%d(%s)  ", p, pn.data.key, pn.data.rest);
            p = pn.next;
        }
        printf("\n");
    }

    //删除动态数组A申请的内存
    free(A);
    //关闭文件
    fclose(fp);
    fclose(fp2);
}

void Insert(char filename[])
{
    struct ElemType x;
    printf("输入待插入元素x的值(作为关键字):\n");
    scanf("%d", &x.key);
    printf("输入数值:\n");
    scanf("%s", x.rest);
    if(!HFSearch(filename,&x))
        HFInsertOne(filename, x);
    else
    {
        printf("该值在哈希表已存在,您将更新这个数值");
        printf("输入待插入元素x的值(作为关键字):\n");
        scanf("%d", &x.key);
        printf("输入数值:\n");
        scanf("%s", x.rest);
    }
}
void Delete(char filename[])
{
    struct ElemType x;
    //定义tag用于保存或查找函数的返回值
    int tag;

    printf("输入待删除元素x的关键字:");
    scanf("%d", &x.key);
    tag = HFDelete(filename, &x);
    if(1 == tag)
    {
        printf("--- 删除成功! %d %s ---\n", x.key, x.rest);
    }
    else
    {
        printf("--- 删除失败 ---\n");
    }//<end if>
}
void Search(char filename[])
{// 待完善,rest(其余信息)查找
    struct ElemType x;
    //定义tag用于保存或查找函数的返回值
    int tag;

    printf("输入待查找元素x的关键字:\n");
    scanf("%d", &x.key);
    tag = HFSearch(filename, &x);
    if(1 == tag)
    {
        printf("---it's sucessful!%d %s ---\n", x.key, x.rest);
    }
    else
    {
        printf("error!\n");
    }
}

void start()
{// 开始
    int number; //选择的功能号表

    //检测散列文件是否存在
    existHash(filename);

    while(1)
    {
        // print meum
        printf("\n");
        printf("\t************************************\n");
        printf("\t*************散列文件 **************\n");
        printf("\t|**1** 初始化散列文件 **************\n");
        printf("\t|**2** 向散列文件中插入一个元素*****\n");
        printf("\t|**3** 从散列文件中删除一个元素 ---|\n");
        printf("\t|**4** 从散列文件中查找一个元素 ---|\n");
        printf("\t|**5** 打印散列文件 ---------------|\n");
        printf("\t|**0** 结束运行 -------------------|\n");
        printf("\t+----------------------------------+\n");
        printf("*--请输入你的选择(0-5):");

        scanf("%d", &number);
        //fflush(stdin);// 防止错误输入
        switch(number)
        {
        case 0:
            return ;
        case 1:
            //初始化散列文件
            InitHashFile(filename);
            break;
        case 2:
            //向散列文件中插入一个元素
            Insert(filename);
            break;
        case 3:
            //从散列文件中删除一个元素
            Delete(filename);
            break;
        case 4:
            //从散列文件中查找一个元素
            Search(filename);
            break;
        case 5:
            //打印散列文件
            HFPrint(filename);

            break;
        default:
            printf("\n--- 输入功能号表错误 ---\n");
            break;
        }   //switch结束o
        //system("pause"); 不能使用 test data 连续输入
        printf("contiue");
        getch();
        system("cls");
    }
}

int main()
{

    start();

    return 0;
}

/*

2
10 a
2
11 b
2
12 c
2
13 d
2
14 e
2
15 f
2
16 g
2
17 h
2
18 i
2
19 p
2
20 k
2
21 l
2
22 m
2
23 n
*/