【编程珠玑-15章】Strings of pearls

15.1 Words

问题:为文档中包含的单词生成一个列表?

可行的办法是自己建立一个散列表,写一个简单的数据结构来记录单词和单词出现的次数。

1)  散列函数的建立

书中给出的源代码提供了一个字符串散列函数。其实很像经典的BKDR算法。C语言描述如下:

unsigned int hash(char *p)
{	unsigned int h = 0;
	for ( ; *p; p++)
		h = MULT * h + *p;
	return h % NHASH;
}

我用java实现BKDRHash(参考:http://java.chinaitlab.com/advance/868618.html):

/*
	 * BKDRHash(String array)的主要思想:种子乘以字符串中每一个字符的大小,总的和即hash,然后取到正整数范围。
	 * 0X7FFFFFFF即Integer.MAX_VALUE,hash&0X7FFFFFFF将符号位置为0,其余各位不变。
* seed可以是31 131 1313 13131 131313 etc..
	 */
	public static int BKDRHash(String array) {
		int seed = 31;
		int hash = 0;
		for (int i = 0; i < array.length(); i++) {
			hash = hash * seed + array.charAt(i);
		}
		return (hash & 0X7FFFFFFF);
	}

*******************************************************************

实现这里的hash

private static final int NHASH=29989,MULT=31;
	/*散列到0~NHASH=29989范围内*/
	public static int hash(String array) {
		int hash=0;
		for (int i = 0; i < array.length(); i++) {
			hash=MULT*hash+array.charAt(i);
		}
		return (hash&0x7FFFFFFF)%NHASH;
	}

2)  完整代码

书中给出的C语言源代码如下:

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

/* wordfreq.c -- list of words in file, with counts */

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

typedef struct node *nodeptr;
typedef struct node {
	char *word;
	int count;
	nodeptr next;
} node;

#define NHASH 29989
#define MULT 31
nodeptr bin[NHASH];

unsigned int hash(char *p)
{	unsigned int h = 0;
	for ( ; *p; p++)
		h = MULT * h + *p;
	return h % NHASH;
}

#define NODEGROUP 1000
int nodesleft = 0;
nodeptr freenode;

nodeptr nmalloc()
{	if (nodesleft == 0) {
		freenode = malloc(NODEGROUP*sizeof(node));
		nodesleft = NODEGROUP;
	}
	nodesleft--;
	return freenode++;
}

#define CHARGROUP 10000
int charsleft = 0;
char *freechar;

char *smalloc(int n)
{	if (charsleft < n) {
		freechar = malloc(n+CHARGROUP);
		charsleft = n+CHARGROUP;
	}
	charsleft -= n;
	freechar += n;
	return freechar - n;
}

void incword(char *s)
{	nodeptr p;
	int h = hash(s);
	for (p = bin[h]; p != NULL; p = p->next)
		if (strcmp(s, p->word) == 0) {
			(p->count)++;
			return;
		}
	p = nmalloc();
	p->count = 1;
	p->word = smalloc(strlen(s)+1);
	strcpy(p->word, s);
	p->next = bin[h];
	bin[h] = p;
}

int main()
{	int i;
	nodeptr p;
	char buf[100];
	for (i = 0; i < NHASH; i++)
		bin[i] = NULL;
	while (scanf("%s", buf) != EOF)
		incword(buf);
	for (i = 0; i < NHASH; i++)
		for (p = bin[i]; p != NULL; p = p->next)
			printf("%s %d\n", p->word, p->count);
	return 0;
}

***************************************************************************

下面是我用Java实现的:

/**
 * 创建时间:2014年9月1日 下午9:31:13 项目名称:Test
 *
 * @author Cao Yanfeng
 * @since JDK 1.6.0_21 类说明:建立字符串散列函数,以链表的方式解决冲突,统计字符串的出现频次
 */
public class WordsFrequency {
	private static final int NHASH = 29989, MULT = 31;
	private static Node[] bin;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// System.out.println(hash("CAOYANFENGhfOWRFRAEOWGNEARFGAT"));
		bin = new Node[NHASH];
		String[] array = { "CaoYanfeng", "agaef", "北京大学", "CaoYanfeng",
				"China", "agaef", "CaoYanfeng", "agaef", "China", "agaef",
				"agaef", "曹艳丰", "CaoYanfeng", "曹艳丰", "北京大学", "CaoYanfeng",
				"China" };
		for (int i = 0; i < array.length; i++) {
			incword(array[i]);
		}
		for (int i = 0; i < bin.length; i++) {
			for (Node node = bin[i]; node != null; node = node.getNext()) {
				System.out.println("单词:" + node.getWordString() + "——数量:"
						+ node.getCount());
			}
		}

	}

	/* 以链表的方式解决hashTable的冲突 */
	public static void incword(String string) {
		int h = hash(string);
		Node node;
		for (node = bin[h]; node != null; node = node.getNext()) {
			if (string.equals(node.getWordString())) {
				node.setCount(node.getCount() + 1);
				return;
			}
		}
		node = new Node();
		node.setWordString(string);
		node.setCount(1);
		node.setNext(bin[h]);
		bin[h] = node;
	}

	/* 采用类似于BKDRHash(String array)的方式将字符串散列到0~NHASH=29989范围内 */
	public static int hash(String string) {
		int hash = 0;
		for (int i = 0; i < string.length(); i++) {
			hash = MULT * hash + string.charAt(i);
		}
		return (hash & 0x7FFFFFFF) % NHASH;
	}
	/* 数组的节点类 */
	private static class Node {
		private String wordString;
		private int count;
		private Node next;

		/**
		 *
		 */
		public Node() {
			// TODO Auto-generated constructor stub
			wordString = null;
			count = 0;
			next = null;
		}

		public String getWordString() {
			return wordString;
		}

		public void setWordString(String wordString) {
			this.wordString = wordString;
		}

		public int getCount() {
			return count;
		}

		public void setCount(int count) {
			this.count = count;
		}

		public Node getNext() {
			return next;
		}

		public void setNext(Node next) {
			this.next = next;
		}

	}

}

15.2 Phrases

问题:给定一个文本文件作为输入,查找其中最长的重复子字符串。

这里用的是后缀数组。如下目标字符串: banana
其长度为6,则后缀数组的长度为6,分别是以b开头的字串(长度为6),以a开头的字串(长度为5),以n开头的字串(长度为4)。。。最后一个是以a开头的字串(长度为1)。

后缀[0] banana

后缀[1] anana

后缀[2] nana

后缀[3] ana

后缀[4] na

后缀[5] a

所以,算法的流程是,先求出字符串的后缀数组,将后缀数组字母排序,然后顺次比较(避免了两两比较)即可。

后缀[0] a

后缀[1] ana

后缀[2] anana

后缀[3] banana

后缀[4] na

后缀[5] nana

最终的比较结果是 后缀[1] 和 后缀[2] 之间存在最长公共字串 ana。

时间复杂度分析:生成后缀数组 O(n),排序 O(nlogn*n)
,最后面的n是因为字符串比较的时候也是一个一个字符进行比较,所以是O(n)。依次检测相邻的两个字符串
O(n * n),总的时间复杂度是 O(n^2*logn),优于暴力方法的 O(n^3)。可以看出,复杂度跟排序相关度大,可以使用倍增算法高效的(nlogn产生排好序的后缀数组,从而提高复杂度。

书中给出的C语言源代码如下:

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

/* longdup.c -- Print longest string duplicated M times */

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

int pstrcmp(char **p, char **q)
{   return strcmp(*p, *q); }

int comlen(char *p, char *q)
{	int i = 0;
	while (*p && (*p++ == *q++))
		i++;
	return i;
}

#define M 1
#define MAXN 5000000
char c[MAXN], *a[MAXN];

int main()
{   int i, ch, n = 0, maxi, maxlen = -1;
    while ((ch = getchar()) != EOF) {
        a[n] = &c[n];
        c[n++] = ch;
    }
    c[n] = 0;
    qsort(a, n, sizeof(char *), pstrcmp);
    for (i = 0; i < n-M; i++)
        if (comlen(a[i], a[i+M]) > maxlen) {
            maxlen = comlen(a[i], a[i+M]);
            maxi = i;
        }
    printf("%.*s\n", maxlen, a[maxi]);
    return 0;
}

*****************************************************************************

我用java实现如下:

/**
 * 创建时间:2014年9月2日 上午10:05:11
 * 项目名称:Test
 * @author Cao Yanfeng
 * @since JDK 1.6.0_21
 * 类说明:  利用后缀数组来查找字符串中最长的重复子串
 */

public class MaxLongDup {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		getMaxLongDup("banana");

	}
	/*这里利用了两个系统函数,Arrays.sort()和string.substring(i)
		 * */
	public static void getMaxLongDup(String string) {
		int length=string.length();
		int maxlen=Integer.MIN_VALUE;
		int position=-1;
		String[] suffix=new String[length];
		for (int i = 0; i < suffix.length; i++) {
	//		suffix[i]=string.substring(i);
suffix[i]=new String(string.substring(i));
		}
		Arrays.sort(suffix);
		for (int i = 0; i < suffix.length-1; i++) {
			int comlen=commonLength(suffix[i], suffix[i+1]);
//			maxlen=(comlen>maxlen)?comlen:maxlen;
			if (comlen>maxlen) {
				maxlen=comlen;
				position=i;
			}

		}
		System.out.println("最大重复子串:"+suffix[position]+";长度为:"+maxlen);
	}
	/*获得相邻的两个字符串的公共长度*/
	public static int commonLength(String string1,String string2) {
		int counter=0;
		char[] arry1=string1.toCharArray();
		int i=0;
		char[] array2=string2.toCharArray();
		int j=0;
		while (i<arry1.length&&j<array2.length) {
			if (arry1[i++]==array2[j++]) {
				counter++;
			}else {
				break;
			}

		}
		return counter;
	}

}

**************************************************************************

注意,我这里在获得后缀数组的时候使用了两个系统函数:

1)  Arrays.sort()。Arrays.sort()对于基本数据类型使用快速排序,而对象的话是堆排序,这里是堆排序;

2)  substring(intindex)。根据以下参考文献的说法,substring并没有生成新的String对象,而是引用了原来String对象的位置索引而已。(参考:http://www.cnblogs.com/tedzhao/archive/2012/07/31/Java_String_substring.html)。所以我使用的是后者而不是前者。

//              
suffix[i]=string.substring(i);

suffix[i]=new String(string.substring(i));

时间: 2024-10-06 19:41:08

【编程珠玑-15章】Strings of pearls的相关文章

一维向量旋转算法 编程珠玑 第二章

看了编程珠玑第二章,这里面讲了三道题目,这里说一下第二题,一维向量旋转算法. 题目:将一个n元一维向量(例数组)向左旋转i个位置. 解决方法:书上讲解了5种方法,自己只想起来2种最简单方法(下面讲的前两种). 1.原始方法. 从左向右依次移动一位,对所有数据平移:这样循环i次,算法最坏时间复杂度达n^2.耗时不推荐. 2.空间换时间. 顾名思义,申请一个i长度的空间,把前i半部分放到申请空间中,再把后面的所有数据向左移动i个位置,最后把申请的空间中的数据放到后半部分.浪费空间,不推荐. 3.杂技

编程珠玑第二章

编程珠玑第二章 A题 给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中一32位整数. 1.在文件中至少存在这样一个数? 2.如果有足够的内存,如何处理? 3.如果内存不足,仅可以用文件来进行处理,如何处理? 答案: 1.32位整数,包括-2146473648~~2146473647,约42亿个整数,而文件中只有40亿个,必然有整数少了. 2.如果采用位数思想来存放,则32位整数最多需要占用43亿个位.约512MB的内存空间. 可以采用前一章的位处理方法.然后判断每个in

编程珠玑第一章习题

第一章习题1解析 以下代码均使用MSVC 6.0编译运行通过,为了便于学习,在原代码的基础上进行了一定的修改. 1.如何使用一个具有库的语言来实现一种排序算法以表示和排序集合,将代码实现可用 1 #include<stdio.h> 2 #include<stdlib.h> 3 int comp(const void *x,const void *y){ 4 return (*(int*)x-*(int*)y); 5 } 6 int a[5]={45,25,64,10,4}; 7 v

稀疏集:编程珠玑第一章第九题

<Programming Pearls> solutions for Column 1中的第9题题解 关键字: Sparse set 原题: The effect of  initializing the vector data[0..n-1] can be accomplised with a signature contained in two additional n-element vectors, from and to ,and an integer top. If the ele

编程珠玑第一章习题6.1000个整数排序

题目描述: 1~1000万的整数,随机挑出1000个整数(可重复),每个整数最多可以出现10次.将这些整数按照升序排序. 分析:      1000个整数,我们可以用1000万个字符按大小来记录它出现的次数,如同 3, 1, 5, 6,5  用5个字符数组表示就是 { 1, 0, 1, 0, 2, 1}.1出现1次,所以第一个字符就用1记录:2出现0次,用0记录,以此类推. 然后,我们按从左往右的顺序,第 i 索引的字符是多少,就输出多少遍该索引(索引即是你要输出的整数).输出结果是:1,3,5

编程珠玑第一章习题答案

习题 1.1      如果不缺内存,如何使用一个具有库的语言来实现一种排序算法? 因为C++有sort,JAVA也有,这里以C++为例给出,记住如果用set集合来排序时,是不可以有元素重复的 代码: #include <iostream> #include <cstring> #include <cmath> #include <queue> #include <stack> #include <list> #include <

编程珠玑 第一章

题目:一个最多包含n个正整数的文件,每个数都小于n,其中n=10^7,且所有正整数都不重复.求如何将这n个正整数升序排列. 约束:最多有1MB的内存空间可用,有充足的磁盘存储空间. 习题2 习题3 实现位向量用于排序. #include<stdio.h> #define N 10000000 #define Shift 5 #define BitPerWord (sizeof(int)*8) #define Mask ((1<<Shift)-1) int a[1+N/BitPerW

编程珠玑第一章中的代码

使用位图法对七位正整数进行排序的算法. #include <stdio.h> #include <stdlib.h> #include <time.h> #define BITSPERWORD 32 #define SHIFT 5 #define MASK 0x1F #define N 100 int a[1 + N/BITSPERWORD]; /* * i >> SHIFT :i / (1 << SHIFT) 即i / (1 <<

java 面向对象编程-- 第15章 集合框架

1.  集合特点:元素类型不同.集合长度可变.空间不固定 2.  java中对一些数据结构和算法进行了封装即集合.集合也是一种对象,用于存储.检索.操作和传输对象. 3.  JCF(Java Collections Framework)是JavaSE中包含的由一组类和接口组成的Java集合框架,其主要功能是用来将存储的数据以某种结构组织,并以特定的方式来访问这些数据,其目标是提供一个处理对象集合的通用框架,减少程序员处理不同对象集合时的编码量. 4.  集合框架包含三个内容:接口.实现类.算法.