BUAA 数据结构总结——第3节(顺序表和链表)

本文介绍了四个基于链表实现的算法:1) 计算连续线段中包含最多线段数的起点;2) 猴子选大王问题的解决方案;3) 一元多项式相乘算法;4) 文本文件的ASCII字符加密方法;5) 英文单词的词频统计。每个算法都有详细的代码实现和示例输入输出。
摘要由CSDN通过智能技术生成

<< 其他专栏文章

创建链表

struct node{
    int data;
    struct node *link;
};
typedef struct node *Nodeptr;
typedef struct node Node;

Nodeptr createList( int n )  /*创建一个具有n个结点的链表 */
{
   /* list是链表头指针, q指向新申请的结点,p指向最后一个结点*/
     Nodeptr  p, q, list=NULL;
   int i;
     for(i=0;i<n;i++){
          q=(Nodeptr)malloc(sizeof(Node));
          q->data=read();     	/* 取一个数据元素 */ 
          q->link=NULL;
          
	       if (list==NULL) 	/*链表为空*/
                list=p=q;
          else
                p->link=q;       /* 将新结点链接在链表尾部 */
                  
	       p=q;
             }
             return list;
        }

第三次作业

3.1连续线段

【问题描述】

平面上两个点(一个点由(x,y)坐标组成)可构成一个线段,两个线段如果有一个端点相同,则可构成一个连续线段。假设构成线段的两个端点为v1(x1,y1)和v2(x2,y2),在此满足x1<x2,其中v1称为线段的起点,v2为线段的终点。同样,对于连续线段来说,在此满足xi<xi+1(i=1…n-1,n为连续线段中的端点数,xi为相应端点的X轴坐标)。输入一组线段(用两个端点的x、y坐标表示线段,线段个数大于等于2,小于等于100),编程计算出连续线段中包含最多线段数的线段,输出相应的线段数和起点位置(注意,不是最长的连续线段,是包含最多线段的连续线段)。例如:

上图中有10个线段,其中5、10、4号线段连成了一条连续线段,线段数3条,起点位置为5号线段的左端点;6、8、2、3、9号线段连成了另一条连续线段,该连续线段包含的线段数最多,为5条,起点位置是6号线段的左端点。

注意:
1)不考虑线段中间相交的情况;
2)不会有三条或三条以上的线段共一个端点;
3)只会出现一条包含最多线段的连续线段;

【输入形式】

先从控制台输入线段数,然后从下一行开始分行输入各线段两个端点的x、y坐标,其中第一个端点的X轴坐标小于第二个端点的X轴坐标,即x1<x2,x、y坐标都用整数表示,不会超过int的表示范围。各整数坐标值间以一个空格分隔。

【输出形式】

先在控制台输出包含最多线段数的连续线段的线段数,然后输出连续线段的起点的x、y坐标,输出数据都以一个空格分隔。

【样例输入】

10
80 75 125 75
60 40 80 55
80 55 90 20
140 120 195 205
10 111 70 165
22 35 43 43
22 175 80 205
43 43 60 40
90 20 125 60
70 165 140 120

【样例输出】

5 22 35

【样例说明】

输入了十个线段,第一个线段两个端点分别为(80,75)和(125,75),其它线段类似,如上图所示,这些线段所构成的连续线段中包含最多线段数的连续线段的线段数为5,起点为(22,35),所以输出:5 22 35。

「代码」
#include <stdio.h>
#include <string.h>

struct line {
    int x1;
    int y1;
    int x2;
    int y2;
    //线段的两个端点为v1(x1,y1)和v2(x2,y2)
};

int main()
{
    
    struct line info[150];
    int n;
    int i,j;
    
    scanf("%d",&n);//读取线段数
    for(i=0;i<n;i++)
    scanf("%d %d %d %d", &info[i].x1, &info[i].y1, &info[i].x2, &info[i].y2);//读取各线段两个端点的x、y坐标
    
    //先排序
    
    struct line tmp;
    for(i=0; i<n; i++)
    for(j=i; j<n; j++){
        if(info[i].x1 > info[j].x1)
        {
            tmp = info[i];
            info[i] = info[j];
            info[j] = tmp;
        }
    }
    
    int count_max=1,x1_max = 0,y1_max = 0;//最长线段
    for(i=0; i<n; i++)
    {
        int x2,y2,count=1;
        x2=info[i].x2;
        y2=info[i].y2;
        for(j=i+1; j<n; j++)
        {
            if( x2==info[j].x1 && y2==info[j].y1 )//i的终点v2与j的起点相等
            {
                count++;//线段数+1
                x2=info[j].x2;
                y2=info[j].y2;
            }
        }
        if(count > count_max)
        {
            count_max=count;
            x1_max=info[i].x1;
            y1_max=info[i].y1;
        }
    }
    
    printf("%d %d %d\n", count_max, x1_max,y1_max);
    //输出包含最多线段数的连续线段的线段数,然后输出连续线段的起点的x、y坐标
    return 0;
}

3.2猴子选大王

【问题描述】(建议用链表实现)

要从n只猴子中选出一位大王。它们决定使用下面的方法:
n只猴子围成一圈,从1到n顺序编号。从第q只猴子开始,从1到m报数,凡报到m的猴子退出竞选,下一次又从退出的那只猴子的下一只开始从1到m报数,直至剩下的最后一只为大王。请问最后哪只猴子被选为大王。

【输入形式】

控制台输入三个整数n,m,q,各整数间以一个空格分隔。

【输出形式】

输出最后选为大王的猴子编号。

【样例输入】

7 4 3

【样例输出】

4

【样例说明】

输入有7只猴子,从第3只猴子开始,从1到4报数。最后编号为4的猴子被选为大王。

「代码」
//  猴子选大王
// 「约瑟夫函数」「链表」
//可以骗数据😊,参照ppt

#include <stdlib.h>
#include <stdio.h>

struct node{
    int data;
    struct node *link;
};
typedef struct node *Nodeptr;
typedef struct node Node;

void josephu( int n, int m, int q );

int main( )
{
    int  n, m, q;
    scanf("%d %d %d",&n, &m, &q);//n个人,从1到m报数,从第q只猴子开始,
    josephu(n,m,q);
    return 0;
}


void josephu( int n, int m, int q )
{
    Nodeptr list,p,r;
    int i;
    list=p=NULL;
    for(i=0;i<n;i++) {
        r=(Nodeptr)malloc(sizeof(Node));
        r->data=i+1;//i+1 从1到n顺序编号
        if(list==NULL)
            list=p=r;
        else {
            p->link=r;
            p=p->link;
        }
    }
    p->link=list;     /* 建立循环链表 */
    r = p;
    for(p=list,i=0;i<q-1;i++,r=p,p=p->link)
    ;
    /* 找到第一个点 */
    while(p->link!=p){
        for(i=0;i<m-1;i++){
            r=p;
            p=p->link;
        }
        r->link=p->link;
//        printf("%d\n",p->data);//报m的人
        free(p);
        p=r->link;
    }
    printf("%d\n", p->data);
}

3.3 多项式相乘

【问题描述】(建议用链表实现)

编写一个程序实现两个一元多项式相乘。

【输入形式】

首先输入第一个多项式中系数不为0的项的系数和指数,以一个空格分隔。且该多项式中各项的指数均为0或正整数,系数和最高幂次不会超过int类型的表示范围。对于多项式 anxn +a n-1 x n-1 +…+ a1x1 + a0x0 的输入方法如下:
an n a n-1 n-1 … a1 1 a0 0
即相邻两个整数分别表示表达式中一项的系数和指数。在输入中只出现系数不为0的项。最后一项的指数后没有空格,只有一个回车换行符。
按照上述方式再输入第二个多项式。

【输出形式】

将运算结果输出到屏幕。将系数不为0的项按指数从高到低的顺序输出,每次输出其系数和指数,均以一个空格分隔,最后一项的指数后也可以有一个空格。

【样例输入】

10 80000 2 6000 7 300 5 10 18 0
3 6000 5 20 8 10 6 0

【样例输出】

30 86000 50 80020 80 80010 60 80000 6 12000 21 6300 10 6020 31 6010 66 6000 35 320 56 310 42 300 25 30 130 20 174 10 108 0

【样例说明】

输入的两行分别代表如下表达式:
10x80000 + 2x6000 + 7x300 + 5x10 + 18
3x6000 + 5x20 + 8x10 + 6
相乘结果为:
30x86000 + 50x80020 + 80x80010 + 60x80000 + 6x12000 + 21x6300 + 10x6020 + 31x6010 + 66x6000 + 35x320 + 56x310 + 42x300 + 25x30 + 130x20 + 174x10 + 108

提示:利用链表存储多项式的系数和指数。

「代码」
#include <stdio.h>
#include <stdlib.h>
struct  Node { //一个多项式节点结构
    int coe; //系数
    int pow; //幂
    struct Node *next;
};
typedef struct Node *Nodeptr;
typedef struct Node Node;

int main()
{
    int a,n,a2,n2;
    char c;
    struct Node *head1,*head2 = NULL,*p1,*p2,*q1,*q2,*p0 = NULL;
    head1 = p1 = NULL;
    do {  //创建一个链表存放第一个多项式
        scanf("%d%d%c", &a, &n, &c);
        q1 =  (Nodeptr)malloc(sizeof(Node));
        q1->coe = a; q1->pow = n; q1->next = NULL;
        if( head1 == NULL)
            head1 = p1 = q1;
        else {
            p1->next = q1;
            p1 = p1->next;
        }
    } while ( c != '\n');
    
    将第二个多项式的每个项与第一个多项式的每个项相乘(系数相乘,指数相加),然后插入第三个链表的相应位置/排序
    
    do { //将第二个多项式的每个项插入到第一个多项式链表中
        scanf("%d%d%c", &a, &n, &c);//生成第二个多项式的一个节点
        for(p1=head1; p1!=NULL; p1=p1->next) {
            
            //将第二个多项式的每个项与第一个多项式的每个项相乘(系数相乘,指数相加)
            a2=a*p1->coe;
            n2=n+p1->pow;
            
            //然后插入第三个链表的相应位置/排序
            q2 = (struct Node *)malloc(sizeof(struct Node));
            q2->coe = a2; q2->pow = n2; q2->next = NULL;
            
            if( head2 == NULL)
                head2 = p2 = q2;//第一次是空链表
            else{
            for(p2=head2; p2!=NULL; p0=p2,p2=p2->next) {
                if(q2->pow > p2->pow) {
                    if(p2==head2) { q2->next = head2; head2 = q2; break; }//插入到头节点前
                    else { q2->next = p2; p0->next = q2; break;} //将q插入到p前
                }
                else if(q2->pow == p2->pow)  { p2->coe += q2->coe; break;}//指数相等,系数相加
            }
            if(p2== NULL)  p0->next = q2;   //将q插入到尾节点后    
            }
        }
    } while ( c != '\n');
    
    for(p2=head2; p2!=NULL; p2=p2->next)
    printf("%d %d ", p2->coe,p2->pow);
    printf("\n");
    return 0;
}

3.4 文件加密(环)

【问题描述】(建议用链表实现)

有一种文本文件加密方法,其方法如下:

1、密钥由所有ASCII码可见字符(ASCII码编码值32-126为可见字符)组成,密钥长度不超过32个字符;

2、先将密钥中的重复字符去掉,即:只保留最先出现的字符,其后出现的相同字符都去掉;

3、将不含重复字符的密钥和其它不在密钥中的可见字符(按字符升序)连成一个由可见字符组成的环,密钥在前,密钥的头字符为环的起始位置;

4、设原密钥的第一个字符(即环的起始位置)作为环的开始位置标识,先从环中删除第一个字符(位置标识则移至下一个字符),再沿着环从下一个字符开始顺时针以第一个字符的ASCII码值移动该位置标识至某个字符,则该字符成为第一个字符的密文字符;然后从环中删除该字符,再从下一个字符开始顺时针以该字符的ASCII码值移动位置标识至某个字符,找到该字符的密文字符;依次按照同样方法找到其它字符的密文字符。当环中只剩一个字符时,则该剩下的最后一个字符的密文为原密钥的第一个字符。

下面以可见字符集只由小写字母组成为例来说明对应密文字符集生成过程。如果密钥为:helloworld,将密钥中重复字符去掉后为:helowrd,将不在密钥中的小写字母按照升序添加在密钥后,即形成字符串:helowrdabcfgijkmnpqstuvxyz,该字符串形成的环如下图所示:

明码的第一个字母为h,h也是环的起始位置。h的ASCII码制为104,先把h从环中删除,再从下一个字母e开始顺时针沿着环按其ASCII值移动位置标识(即:在字母e为移动第1次,共移动位置标识104次)至字母w,则h的密文字符为w。w的ASCII码制为119,然后将w从环中删除,再从下一个字母r开始顺时针沿着环移动位置标识119次至字母为l,则w的密文字符为l。依次按照同样方法找到其它字母的密文字符。环中剩下的最后一个字母为x,则x的密文字符为明码的第一个字母h。按照这种方法形成的密文转换字符表为:

biao.jpg

上方为原文字符,下方为对应的密文字符。由所有ASCII可见字符集组成的字符集密文字符生成方式与上例相同。

编写程序实现上述文件加密方法。密钥从标准输入读取,待加密文件为当前目录下的in.txt文件,该文件中的字符若是可见字符,则按照上述方法进行加密,否则原样输出(例如:回车换行符),加密后生成的密文文件为当前目录下的in_crpyt.txt。

【输入形式】

密钥是从标准输入读取的一行字符串,可以包含任意ASCII码可见字符(ASCII码编码值32-126为可见字符),长度不超过32个字符。

【输出形式】

加密后生成的密文文件为当前目录下的in_crpyt.txt。

【样例输入】

C Programming(Second Edition)

假设in.txt文件内容为:

This book is meant to help the reader learn how to program in C. It is the definitive reference guide, now in a second edition. Although the first edition was written in 1978, it continues to be a worldwide best-seller. This second edition brings the classic original up to date to include the ANSI standard.

From the Preface:

【样例输出】

in_crpyt.txt文件内容为:

KgklW#33>WklWA\^M8W83Wg\Z,W8g\WP\^u\PWZ\^PMWg3jW83W,P30P^AWkMWX5W.8WklW8g\Wu\EkMk8kt\WP\E\P\MR\W0-ku\+WM3jWkMW^Wl\R3MuW\uk8k3M5WIZ8g3-0gW8g\WEkPl8W\uk8k3MWj^lWjPk88\MWkMW'71G+Wk8WR3M8kM-\lW83W#\W^Wj3PZujku\W#\l8Jl\ZZ\P5WKgklWl\R3MuW\uk8k3MW#PkM0lW8g\WRZ^llkRW3Pk0kM^ZW-,W83Wu^8\W83WkMRZ-u\W8g\WIOY.Wl8^Mu^Pu5

4P3AW8g\WdP\E^R\(

【样例说明】

输入的密钥为C Programming(Second Edition),由该密钥生成的字符串环中字符依次为:

C Progamin(SecdEt)!"#$%&'*+,-./0123456789:;<=>?@ABDFGHIJKLMNOQRTUVWXYZ[\]^_`bfhjklpqsuvwxyz{|}~

形成的字符转换字符表(第一行为原ASCII字符,第二行为对应的密文字符)为:image.png
按照该密文转换字符表将in.txt中的内容转换成加密字符输出到in_crpyt.txt中。

stdin为标准输入输出可用于文件操作函数

「代码」
//将密钥和其它可见字符(ASCII码32-126)连成环,需记忆环末尾的指针
//参考上个密钥题
//密文字符建立数组
//文件操作参考上个密钥题
    
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct node{
    char c;
    struct node *link;
};
typedef struct node *Nodeptr;
typedef struct node Node;

char key[40],key_noRepetition[40],cipher[130];//读取的密钥,去重的密钥,密文字符(索引与原字符的ascii对应)
int ASCII[130];//(ASCII码32-126),用来检查是否重复

int main( )
{
    char  c,first,last;//first用来保存第一个字符,last保存上一个字符
    int i,j;
    FILE *fi, *fp;
    
    fgets(key,33,stdin);//读取密钥,读入了‘\n’
//    gets(key);
    for( i=0,j=0; key[i] != '\0' && key[i] != '\n'; i++ )
    {
        //密钥字符去重
        if (ASCII[key[i]]!=1)
        {
            key_noRepetition[j]=key[i];
            ASCII[key[i]]=1;//标记该字母已出现
            j++;
        }
    }
    
    //将密钥和其它可见字符(ASCII码32-126)连成环,需记忆环末尾的指针
    Nodeptr list,p,r;
    list=p=NULL;
    for(i=0; key_noRepetition[i] != '\0' ;i++) {
        //将密钥连成链表
        r=(Nodeptr)malloc(sizeof(Node));
        r->c=key_noRepetition[i];
        if(list==NULL)
            list=p=r;
        else {
            p->link=r;
            p=p->link;
        }
    }
    for(i=32; i<=126 ;i++) {
        //将其它可见字符(ASCII码32-126)连成链表
        if (ASCII[i]!=1)
        {
            r=(Nodeptr)malloc(sizeof(Node));
            r->c=i;
            
                p->link=r;
                p=p->link;
        }
        
    }
    p->link=list;     /* 建立循环链表 */
    r = p;  //记忆环末尾的指针
    
    
    //测试,字符串环
//    p=list;
//    int n=0;                        //链表的长度置初值0
//    if(list == NULL) return 0;
//    do{
//        printf("%c",p->c);
//        p=p->link;
//        n++;
//    }while(p!=list);//字符串环is ok!
//    printf("\n");
    
    /*
     设原密钥的第一个字符(即环的起始位置)作为环的开始位置标识,先从环中删除第一个字符(位置标识则移至下一个字符),再从下一个字符开始以第一个字符的ASCII码值移动位置标识至某个字符,该字符成为第一个字符的密文字符;//参考「第二题」,出列的为该密文字符
     p是last的密文
     
     然后从环中删除该字符,再从下一个字符开始以该字符的ASCII码值移动位置标识至某个字符,找到该字符的密文字符;依次按照同样方法找到其它字符的密文字符。当环中只剩一个字符时,则该剩下的最后一个字符的密文为原密钥的第一个字符。//「第二题」,即最后剩下的人
     p的密文是first
     */
    
    //设原密钥的第一个字符(即环的起始位置)作为环的开始位置标识,先从环中删除第一个字符(位置标识则移至下一个字符)
    p=list;
    first=p->c;
    r->link=p->link;
    last=p->c;
    free(p);
    p=r->link;
    
    while(p->link!=p){
        for(i=0;i<last-1;i++){
            r=p;
            p=p->link;
        }//再从下一个字符开始以第一个字符的ASCII码值移动位置标识至某个字符
        r->link=p->link;
        cipher[last]=p->c;//即上一个字符的密文字符
        last=p->c;
        free(p);
        p=r->link;
    }
    cipher[last]=p->c;/*【特别注意⚠️】倒数第二个字符的密文是最后一个字符(因为while的条件导致了没被赋值)*/
    //当环中只剩一个字符时,则该剩下的最后一个字符的密文为原密钥的第一个字符
    cipher[p->c]=first;

    //测试,密文转换字符表
//    for(int j=32;j<=126;j++)
//    printf("%c",j);
//    printf("\n");
//    for(int j=32;j<=126;j++)
//    printf("%c",cipher[j]);


    if((fi = fopen("in.txt", "r")) == NULL)
    {
        printf("Can't open file encrypt.txt !\n");
        return 404;
    }//文件打开失败报错
    fp = fopen("in_crpyt.txt", "w");//输出文件

    c=fgetc(fi);
    while(c!=EOF)
    {

        if (32<=c && c<=126)
        {
            fputc(cipher[c], fp);
        }
        else fputc( c , fp);
        c=fgetc(fi);
    }

    return 0;
}

3.5 词频统计(数组或链表实现)

【问题描述】

编写程序统计一个英文文本文件中每个单词的出现次数(词频统计),并将统计结果按单词字典序输出到屏幕上。

注:在此单词为仅由字母组成的字符序列。包含大写字母的单词应将大写字母转换为小写字母后统计。

【输入形式】

打开当前目录下文件“article.txt”,从中读取英文单词进行词频统计。

【输出形式】

程序将单词统计结果按单词字典序输出到屏幕上,每行输出一个单词及其出现次数,单词和其出现次数间由一个空格分隔,出现次数后无空格,直接为回车。

【样例输入】

当前目录下文件article.txt内容如下:

“Do not take to heart every thing you hear.”
“Do not spend all that you have.”
“Do not sleep as long as you want;”

【样例输出】

all 1
as 2
do 3
every 1
have 1
hear 1
heart 1
long 1
not 3
sleep 1
spend 1
take 1
that 1
thing 1
to 1
want 1
you 3

【样例说明】

按单词字典序依次输出单词及其出现次数。

「代码」
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>

# define MAXWORD  50

struct node {
    char word[MAXWORD];
    int count;
    struct node *link;
} ; //单词表结构
struct node *Wordlist = NULL; //单词表头指针

int getWord(FILE *bfp,char *w);
int searchWord(char *w);
int insertWord( struct node *p, char *w);

int main()
{
    char filename[32]={"article.txt"};
    char word[MAXWORD];
    FILE *bfp;
    struct node *p;

    if((bfp = fopen(filename, "r")) == NULL){ //打开一个文件
        fprintf(stderr, "%s  can’t open!\n",filename);
        return -1;
    }
    
    while( getWord(bfp,word) != EOF) //从文件中读入一个单词
        if(searchWord(word) == -1) { //在单词表中查找插入单词
            fprintf(stderr, "Memory is full!\n");
            return -1;
        }
    
    for(p=Wordlist; p != NULL; p=p->link) //遍历输出单词表
    printf("%s %d\n", p->word, p->count);

    return 0;
}

/*在链表中p结点后插入包含给定单词的结点,同时置次数为1*/
int insertWord(struct node  *p, char *w)
{
    struct node  *q;

    q = (struct node * )malloc(sizeof(struct node));
    if(q == NULL) return -1; //没有内存空间
    strcpy(q->word, w);
    q->count = 1;
    q->link = NULL;
    if(Wordlist == NULL) //空链表
        Wordlist = q;
    else if (p == NULL){ //插入到头结点前
        q->link = Wordlist;
        Wordlist = q;
    }
    else {
        q->link = p->link;
        p->link = q;
    }
    return 0;
}

/*在链表中查找一单词,若找到,则次数加1;否则将该单词插入到有序表中相应位置,同时次数置1*/
int searchWord(char *w)
{
    struct  node *p, *q=NULL; //q为p的前序结点指针
    for(p=Wordlist; p != NULL; q=p,p=p->link){
        if(strcmp(w, p->word) < 0)
            break;
        else if(strcmp(w, p->word) == 0){
            p->count++;
            return 0 ;
        }
    }
    return insertWord(q, w);
}

/*从文件中读入一个单词(仅由字母组成的串),并转换成小写字母*/
int getWord(FILE *bfp,char word[])
{
    int i=0;
    char temp;

    while((temp=fgetc(bfp))!=EOF){
        if(isalpha(temp)){
            word[i]=tolower(temp);
            i++;
        }
        else if(i>0){//说明i中已经至少有一个字符
            word[i]='\0';
            return 0;
        }
    }
    return EOF;
}
//getc,是字母一直读,否则break

套数据模版

// 套数据模版
# include <stdio.h>
# include <stdlib.h>
int main(int argc, const char * argv[]) {
    FILE *bfp;
    char filename[32]={"article.txt"};
    char c;
    if((bfp = fopen(filename, "r")) == NULL){ //打开一个文件
            fprintf(stderr, "%s  can’t open!\n",filename);
            return -1;
        }
    while((c=  fgetc(bfp) ) != EOF)
        printf("%c",c);
    printf("Hello, World!\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值