第十五章 字符串(三)

C Primer Plus第十五章编程练习,仅供参考 第十五章编程练习 本章第一个编程练习的内容是让我们将二进制字符串转换为一个数值,传递字符串指针,逻辑并不复杂,完整程序代码以及运行结果如下: #include<stdio.h> #include<string.h> #include<math.h> int nfun(char * ptr); int main(void){ char * pbin = "01001001"; int number; number = nfun(pbin); 阅读详情

    在十五章的最后展示了一个挺复杂的问题,也是利用到了后缀数组的结构,解决的问题是:如何生成随机文本。因为算法的最终确定是“通过文本内容的前K个字符或单词来判断下一个字符或单词是什么”这个思想来进行的,所以书中说这是一个“具有固定转化概率的有限状态马尔科夫链”,但这个术语究竟是什么意思我还没搞清楚。但是总结说来比较简洁的描述方法就是:生成k阶单词或字符级别的随机文本(马尔科夫链)。

    在确定了问题和有了相应的解题思路之后我们就可以着手解决这个问题了。源码共展示了三个程序:1,k阶单词级别随机文本。2,k阶单词级别随机文本(哈希加速法)。3.k阶字符级别随机文本。

    第一个程序的思路是:1,首先定义比较大的字符数组来存储单词内容,利用scanf()函数读入单词,建立后缀数组。2.后缀数组利用qsort()排序。3.输出文中的前k个单词,设置文本指针为指向字符数组首地址,利用改进的二分查找算法查找在排序后的后缀数组中前k个单词首次出现的位置。4.不断地寻找符合条件的新的位置并利用一个概率算法等概率地选择出其中的一个位置,输出第K+1个单词。5.利用函数重新设置文本的指针指向字符数组的第二个元素,不断循环,终止条件是此次单词为文本的最后一个单词,其后面再无其他单词,故无法继续进行下去,此时终止循环(设置循环次数)。

    其中比较重要的改进是对qsort()中的比较函数wordcmp()进行改进,加入了考虑k个单词的条件,其中也是利用到了scanf()函数为每个单词提供的结束符'\0'来判断已经比较过的单词个数。再有就是概率改进的二分查找程序,其改进之处在于“比如,在整数t多次出现在整数数组x[0 ,, n-1]的情况下,找出t第一次出现的位置。”而普通的二分查找算法会返回任意的一个位置,这个改进的原理是修改了程序的循环不变式,让上界u代表的数x[u]>=t,而不是x[u]>t。文中还用到了一个比较小巧的函数char *skip(char *s, int i),其主要功能是返回一个指针,指向s之后的第i个单词(单词级别)。

    说明一下其中的概率选择算法,其来源是一道习题,问题是这样的:具体说来,如何在事先不知道文本文件行数的情况下读取该文件,从中随机选择并输出一行?

    答案是这样的:

    我们总选择第1行,并以概率1/2选择第2行,以概率1/3选择第3行,以此类推。在这一过程结束时,每一行的选中概率是想等的(都是1/n,其中n是文件的总行数):

    i=0

    while more input lines

         with probability 1.0/++i

             choice = this input line

    print choice

    总之这个程序很复杂,值得总结的地方还有很多,今天有点儿着急,所以吸收的还不是很好,程序摹写得也很差劲。所以最后还是要附上源码。

// 生成单词级别的2阶随机文本, k=2
// generate random text from input document

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

int k=2;
char inputchars[4300000];
char *word[800000];  // 
int nword=0;

int sortcmp(char **p, char **q)
{
    return wordcmp(*p, *q);
}

// compare k words in the front
int wordcmp(char *p, char *q)
{
    int n=k;
    /*
    while(*p++ == *q++)
    {
             if(*p == '\0' && --n ==0)
                 return 0;
    }
    */
    for(;*p == *q; p++,q++)
    {
            if(*p == '\0' && --n ==0)
                 return 0;
    }
    
    return *p-*q;
}

char *skip(char *t, int n)
{
     for(;;)
     {
            if(*t=='\0' && --n==0)
                break;
            ++t;
     }
     
     return ++t;
}

int main()
{
    int i;
    word[0]=inputchars;
    while(scanf("%s",word[nword]) != EOF)
    {
                       word[nword+1]=word[nword]+strlen(word[nword])+1;
                       nword++;
    }
    // set the last word to '\0'  不明白为什么要设置k位,感觉1位就够了    
    for(i=0;i<k;i++)
        word[nword][i]='\0';
    for(i=0;i<k;i++)
        printf("%s ",word[i]);
    qsort(word, nword, sizeof(char *), sortcmp);
    
    //int l, u, occurence=0, wordsleft, m;
    int l, u, wordsleft, m;
    char *p=word[0], *q; 
    
    //
    srand((unsigned)time(NULL));
    for(wordsleft=1000;wordsleft>0;--wordsleft)
    {
        l=-1;
        u=nword;
        while(l+1 != u)
        {
              m=(l+u)/2;
              if(wordcmp(word[m],p)<0)
                  l=m;
              else
                  u=m;
        }

        // 随机选择一行满足条件的文本的算法 
        int count=0;
        for(;;)
        {
            if(wordcmp(word[u],p)!=0)
               break;
            //if(rand()%++occurence == 0)
            if(rand()%(count+1) == 0)
            {
                            p=word[u];
                            count++;
            }
            u++;
        }
        /*
        for(i=0;wordcmp(p, word[u+i])==0;i++)
        {
                                          if(rand()%(i+1)==0)
                                              p=word[u+i];
        }
        */
        
        q=skip(p,1);
        if(*(q+k-1)=='\0')
            break;
        //putchar(*(q+k));
        //printf("%s\n",skip(p,k-1));  // 可以利用这个表达式输出,word[]中各项都是由'\0'结束的单词 
        printf("%s\n",skip(q,k-1));  // 可以利用这个表达式输出,word[]中各项都是由'\0'结束的单词 
        p=q;
    }

    return 0;
}

需要注意的问题:在构造后缀数组时本例是直接向后缀数组中输入内容,因为事先已经在字符数组inputchars[]申请完空间了,所以需要做的就是在word[]字符指针数组inputchars[]字符指针中找到一种关联的关系。


程序的运行效果如下:因为设置了srand()种子,所以运行效果会有不同。



    第二个程序利用哈希表的方法实现了程序的加速,散列函数取代二分搜索,使平均的运行时间从O(nlogn)降到了O(n)。程序的整体流程没有发生改变,比较有趣儿的地方是使用了一个哈希表整型数组bin[NHASH]和next[MAXWORD]实现了一个链表的功能,其中的思想我理解了半天才明白过来,也是挺别扭的。bin[]中存储的是当前链表的第一个元素在word[]后缀数组中的下标,而next[]中存储的是当前链表的下一个元素在word[]中的位置。


// 生成单词级别的2阶随机文本, k=2
// generate random text sped up with hash tables

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

#define NHASH 499979
#define MAXWORD 800000
#define MULT 31

int k=2;
char inputchars[4300000];
char *word[MAXWORD];  
int nword=0;
int bin[NHASH];
int next[MAXWORD];

/*
int hash(char *p)
{
    int h=0, i;

    for(i=0;*p && i<k;i++,p++)
        h=h*MULT + *p;
    
    return h%NHASH;
}
*/

unsigned int hash(char *ptr)
{
         unsigned int h=0;
         char *p=ptr;     //定义另一个指针变量,防止原来的指针发生改变 
         int n;
         for(n=k;n>0;++p)
         {
                      h=h*MULT + *p;
                      if(*p == '\0')
                          n--;
         }
                      
         return h%NHASH;
         }

int sortcmp(char **p, char **q)
{
    return wordcmp(*p, *q);
}

int wordcmp(char *p, char *q)
{
    int n=k;
    for(;*p == *q; p++,q++)
    {
            if(*p == '\0' && --n ==0)
                 return 0;
    }
    
    return *p-*q;
}

char *skip(char *t, int n)
{
     for(;;)
     {
            if(*t=='\0' && --n==0)
                break;
            ++t;
     }
     
     return ++t;
}


int main()
{
    int i, j;
    word[0]=inputchars;
    while(scanf("%s",word[nword]) != EOF)
    {
                       word[nword+1]=word[nword]+strlen(word[nword])+1;
                       nword++;
    } 
    for(i=0;i<NHASH;i++)
        bin[i]=-1;
    for(i=0;i<nword-k+1;i++)
    {
                        j=hash(word[i]);
                        next[i]=bin[j];
                        bin[j]=i;
    }
    for(i=0;i<k;i++)
        printf("%s ",word[i]);
    
    int wordsleft, count;
    char *phrase=inputchars;
    srand((unsigned)time(NULL));
    for(wordsleft=1000;wordsleft>0;--wordsleft)
    {
            count=0;
            //for(j=bin[hash(phrase)];j>0 && wordcmp(phrase, word[j])==0;j=next[j])
            for(j=bin[hash(phrase)];j>=0;j=next[j])
            {
                                               if((wordcmp(phrase, word[j])==0) && (rand()%(++count)==0))
                                                   phrase=word[j];
                                               //count++;
            }
            
            phrase=skip(phrase, 1);
            if(strlen(skip(phrase, k-1))==0)
                break;
            printf("%s\n",skip(phrase, k-1));
    }
    
    return 0;
}
需要注意的问题:1,哈希函数的设计。2,边界的控制,尤其是在初始化bin[]哈希表数组时。3,在哈希表中寻找程序时的判断标准。不应该把wordcmp()写在程序的条件判断部分,因为有可能有hash()值相同而wordcmp()不同,而顺序不一致可能导致有些数据没有访问到。程序的运行效果和上图类似。


    最后一个程序没有用到后缀数组,很神奇地在一个char型数组中就完成了所有的工作,这个程序有些难度,我理解了挺长时间,尤其是在边界控制上,真的需要小心翼翼,而且在没有利用后缀数组的前提下在这么短的代码中完成工作,我觉得算法的设计很重要,但整体上的思路还是和上述程序有类似的。

// generate letter-level random text from input text  5阶文本 
//  

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

char x[5000000];

int main()
{
    int n=0;
    char c;
    while((c=getchar())!=EOF)
        x[n++]=c;
    x[n]='\0';
    
    int max, k=5, eqsofar, i;
    char *p=x, *q, *nextp;
    srand((unsigned)time(NULL));
    for(max=2000;max>0;--max)
    {
                             eqsofar=0;
                             //for(q=x;q<q+n-k;q++)//
                             for(q=x;q<x+n-k+1;q++)
                             {
                                                   for(i=0;i<k && *(p+i)==*(q+i);i++)
                                                               ;
                                                   if(i==k)
                                                   {
                                                           if(rand()%(++eqsofar)==0)
                                                           nextp=q;
                                                   }
                                                   /*
                                                   //c=*(p+k);
                                                   if(c=='\0')
                                                       break;
                                                   putchar(c);
                                                   p=nextp+1;
                                                   */
                             }
                             c=*(nextp+k);
                             if(c=='\0')
                                 break;
                             putchar(c);
                             p=nextp+1;
    }
    
    return 0;
}
需要注意的问题很多,就不举了。



最后贴上一段程序吧,是第一个程序的源码,变量定义和函数写的需要学习:

/* Copyright (C) 1999 Lucent Technologies */
/* From 'Programming Pearls' by Jon Bentley */

/* markov.c -- generate random text from input document */

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

char inputchars[4300000];
char *word[800000];
int nword = 0;
int k = 2;

int wordncmp(char *p, char* q)
{	int n = k;
	for ( ; *p == *q; p++, q++)
		if (*p == 0 && --n == 0)
			return 0;
	return *p - *q;
}

int sortcmp(char **p, char **q)
{	return wordncmp(*p, *q);
}

char *skip(char *p, int n)
{	for ( ; n > 0; p++)
		if (*p == 0)
			n--;
	return p;
}

int main()
{	int i, wordsleft = 10000, l, m, u;
	char *phrase, *p;
	word[0] = inputchars;
	while (scanf("%s", word[nword]) != EOF) {
		word[nword+1] = word[nword] + strlen(word[nword]) + 1;
		nword++;
	}
	for (i = 0; i < k; i++)
		word[nword][i] = 0;
	for (i = 0; i < k; i++)
		printf("%s\n", word[i]);
	qsort(word, nword, sizeof(word[0]), sortcmp);
	phrase = inputchars;
	for ( ; wordsleft > 0; wordsleft--) {
		l = -1;
		u = nword;
		while (l+1 != u) {
			m = (l + u) / 2;
			if (wordncmp(word[m], phrase) < 0)
				l = m;
			else
				u = m;
		}
		for (i = 0; wordncmp(phrase, word[u+i]) == 0; i++)
			if (rand() % (i+1) == 0)
				p = word[u+i];
		phrase = skip(p, 1);
		if (strlen(skip(phrase, k-1)) == 0)
			break;
		printf("%s\n", skip(phrase, k-1));
	}
	return 0;
}


C Primer Plus (第六版) 第十五章_编程练习答案 no1.c //编写一个函数,把二进制字符串转换为一个数值.例如,有下面语句: //char * pbin = "01001001" ; //那么把pbin作为参数传递给函数后,他应该返回一个int类型的值25; # include <stdio.h> # include <limits.h> # include <string.h> # include ... 阅读详情

相关推荐

CPrimer Plus第6版第十五章编程练习答案(C语言)(自学记录)

第一题 #define _CRT_SECURE_NO_WARNINGS //第十五章,编程练习题,第一题 #include <stdio.h> #include <stdlib.h> #include <string.h> int printStrInt(char* str); char* fget_s(char* st, int n); int main(void) { int ch; char str[100]; printf("请输入一个二进制字符串:");

这个很可以的博客 373

数据结构之字符串

字符串算法简介 字符串排序 键索引计数法 应用领域: 1、通过字符串一个字符排序。 2、将全班同学按组分类。 3、通过地区码排序电话号码。 这是一种用于小整数键的排序方法。只需要理解下面的实际问题即可理解键索引计数法排序。 对于ASCII字符,我们可以直接用ASCII对应的数值作为键,初始化count数组,然后在数组里面记录,每一个ASCII字符出现的次数...

一个菜鸟的博客 986

开源低场MRI系统构建:从原理到三天实现

磁共振成像(MRI)作为现代医学影像的核心技术,其高场强系统存在成本和技术门槛问题。低场MRI通过开源硬件和模块化设计实现技术民主化,采用永磁体替代超导磁体可大幅降低系统复杂度。本文以H型永磁体设计为例,结合MaRCoS开源控制库和PyPulseq脉冲序列工具,展示了如何快速构建教育级MRI系统。关键技术包括遗传算法优化的匀场方案、CNC加工的梯度线圈,以及基于Red Pitaya的SDR控制架构。这种轻量化方案特别适合医学物理教学和脉冲序列开发验证,为MRI技术在资源有限地区的普及提供了新思路。

weixin_30470643的博客 449

自然语言处理中的文本生成

1.背景介绍 自然语言处理(NLP)是计算机科学和人工智能领域的一个重要分支,旨在让计算机理解、生成和处理人类语言。文本生成是NLP中的一个重要任务,旨在根据给定的输入生成自然流畅的文本。这篇文章将详细介绍文本生成的背景、核心概念、算法原理、实例代码以及未来趋势和挑战。 2.核心概念与联系 在自然语言处理中,文本生成是指计算机根据输入的信息(如语义、上下文等)生成自然流畅的文本。这个过程涉及...

AI天才研究院 2782

YOLOv5s模型训练全流程解析:从数据准备到实战检测

本文详细解析了YOLOv5s模型训练的全流程,涵盖从环境搭建、数据准备与格式转换,到模型训练、参数调优及实战检测的完整步骤。文章提供了具体的代码示例和常见问题解决方案,旨在帮助开发者,特别是新手,高效掌握YOLOv5s模型训练的核心技术,并成功应用于实际检测任务中。

weixin_29041767的博客 331

飞书文档API深度使用指南:如何用n8n社区节点绕过HTTP Request的复杂配置

本文详细解析了如何利用n8n社区节点简化飞书文档API的集成过程,绕过复杂的HTTP Request配置。通过n8n-feishu-lite社区节点,开发者可以轻松实现飞书文档的创建、编辑和查询操作,大幅提升自动化工作流的效率。文章还提供了高级应用场景和性能优化建议,帮助开发者更好地利用飞书开放平台的API功能。

weixin_29227585的博客 83

【小知识点】Python 随机生成一个汉字,提供了多种办法,目的竞然是用于创建头像

直接使用 Unicode 编码,会出现很生僻字,在实战中可以使用部分策略解决该问题,例如找一篇长文,将其存储到一个文本文件中,然后使用 Python 的读写文件功能来读取文件中的汉字。生成随机汉字的模块不是 Python 自带的功能,但是你可以使用 Python 的 random 模块来生成随机数,然后使用 Unicode 编码来获取对应的汉字。中读取文字,这里再补充一个步骤,由于随机生成的文本中会有标点符号,所以需要进行去除。项目时,碰到一个随机头像的需求,这里用汉字去随机生成。

梦想橡皮擦,专栏100例写作模式先行者,现象级专栏 《Python 爬虫 100 例》作者、《滚雪球学 Python 专栏》原创者 1万+

Anaconda创建python环境常见命令

Anaconda功能庞大,其可以理解为一个工具,也是一个可执行命令。Anaconda可以创建多个python环境,且python环境间相互独立。

m0_59405106的博客 1万+

哈希算法之字符串随机生成

字符串随机生成代码(注:此部分也是改编网络上某个哥们儿的代码,一时找不到出处了,谁看到了请告知,我添加上引用,感谢尊重他人劳动成果): #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include &l...

weixin_33755554的博客 717

别再为淘宝滑块发愁了!一个Python脚本自动获取x5sec cookie的保姆级教程

本文提供了一套完整的Python自动化解决方案,帮助开发者轻松应对淘宝滑块验证码问题。通过模拟浏览器行为、精准识别滑块缺口位置和生成拟人化移动轨迹,脚本能够自动获取关键的x5sec cookie,显著提升爬虫效率。教程涵盖环境配置、验证机制分析、代码实现及反检测策略,是解决淘宝滑块验证的实用指南。

weixin_30300225的博客 574

静态与动态LACP链路聚合:原理、配置与排错实战指南

链路聚合技术是提升网络带宽与可靠性的核心方案,通过将多个物理端口逻辑捆绑,实现带宽叠加与链路冗余。其原理基于IEEE 802.3ad标准,通过负载均衡算法将数据流分布到不同物理链路,既避免了数据包乱序,又提升了整体吞吐量。该技术的核心价值在于消除单端口瓶颈、提供毫秒级故障切换,并能简化网络拓扑管理。在企业服务器接入、数据中心互联及虚拟化平台等场景中,链路聚合已成为构建高可用网络的基础。实践中,动态LACP模式通过协议报文协商,能主动检测链路故障并防止配置错误导致的网络环路,相比静态聚合更为可靠。本文将深入解

weixin_31412781的博客 266

查找子字符串个数的标准C函数(要求不含库函数)

int Count(char *const a,char *const b) {//a为主串,b为子串 char *p=a,*q=b;  int count=0;  while(*p)        {   if ((*p==*q)&&(*q)) //匹配条件             {    p++; q++;      }            else

maomao4024的专栏 1007

RV1126B SDK移植实战:从DDR配置到USB烧写全解析

本文详细解析了RV1126B SDK移植的核心实战流程,重点攻克了DDR初始化失败与USB烧写两大难题。文章从理解启动流程入手,提供了DDR配置错误的排查步骤、SDK编译配置要点,并深入讲解了USB烧写的完整操作与常见问题解决方案,旨在帮助开发者高效完成系统移植与稳定运行。

weixin_29164497的博客 542

第十五章上机练习3

//上机练习3 import java.util.Scanner; public class MM { public int show(String input,String word){ int count=0; for(int i=0;i<=input.length()-1;i++){ String a=input.substring(i,i+1); if(a

qq_36074064的博客 3288

Docker-Nvidia(NVIDIA Container Toolkit)

安装NVIDIA Container Toolkit工具,支持docker使用GPU。

周陽讀書 3820

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

第十五章字符串

一、如何定义声明一个字符串? 1.以前的写法:数据类型 变量名=赋值; String name="张三"; 2.现在的写法:类名 对象名=new 类名(); 为什么还可以new?因为字符串本身就是一个class类,属于引用类型,使用时就必须要用new关键字创建。 String name=new String("张三");二、字符串的9个常用方法 只需要会使用即可: 1.length() 获取长度 返回int类型

m0_72658137的博客 180

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

第十五章 ObjectScript - 字符串函数

要使用数组中的单个节点,可以使用前面描述的任何函数。

yaoxin521123的博客 541

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

第十五章 字符串

字符串一、字符串使用基础二、复杂的字符串处理三、StringBuffer类的使用 一、字符串使用基础 字符串是非常常用的一种数据类型 日常中的很多信息都可以与字符串表示:密码、用户名、文章标题… 定义并初始化字符串: String str = "abc"; String str = new String();//等同于String str =""; String str = new String("abc");//等同于String str = "abc"; Stri

weixin_56258427的博客 201

第十五章 字符串

package com.tt; import java.util.Scanner; public class S4 { public static void main(String[] args) { StringBuffer sb=new StringBuffer(); int num=100; StringBuffer b1=sb.append("青春无悔");

qq_36074150的博客 1436

第十五章 字符串本地化和消息字典(一)

本文概述了字符串本地化,并描述了如何导出、导入和管理消息字典。当本地化应用程序的文本时,会创建一种语言的文本字符串清单,然后当应用程序区域设置不同时,建立约定以另一种语言替换这些消息的翻译版本。支持以下本地化字符串的过程:这种机制各不相同,但最常见的机制是 宏。代替硬编码的文字字符串,开发人员包含 宏的实例,为宏参数提供如下值: 替换为 编译代码时,编译器会在消息字典中为 宏的每个唯一实例生成条目。 消息字典是全局的,因此可以在管理门户中轻松查看(例如)。有一些类方法可以帮助完成常见任务。结果是一

yaoxin521123的博客 507

CLR via C# 第十四章—第十五章

CLR摘要

2301_79124729的博客 989

第十五章 字符串

众所周知,java提供了很多字符串截取的方式。下面就来看看大致有几种。 1.split()+正则表达式来进行截取。 将正则传入split()。返回的是一个字符串数组类型。不过通过这种方式截取会有很大的性能损耗,因为分析正则非常耗时。 String str = "abc,12,3yy98,0"; String[] strs=str.split(","); for(int i...

ansixue3661的博客 239

C Primer Plus 第十五章 课后答案

目录   复习题 1.把下面的十进制转换为二进制: a.3        b.13        c.59        d.119 2.将下面的二进制值转换为十进制、八进制和十六进制的形式: a.00010101        b.01010101        c.01001100        d.10011101 3.对下面的表达式求值,假设每个值都为8位: a.~3   ...

asdgyy的博客 4755

python教程知识(第十五章)

python知识

zzj13283671195的博客 732
上一篇: 第十五章 字符串(二)
下一篇: Socket工作原理及流程
ProfessionalEngineer
博客等级 码龄19年 295粉丝 154原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值