## 一：字符串的排序

### 1：小整数键——键索引计数法

1. 计算键出现的频率
2. 将频率转换为在排序结果中的起始索引位置
3. 在辅助数组中开始排序
``````public class KeyIndexSort {

//字符串
private Alphabet[] alphabets;
//记录键的频率
private int[] count;
//临时数组
private Alphabet[] temp;

public KeyIndexSort(Alphabet[] alphabets,int digit,int indexLen){
count = new int[indexLen + 1];
temp = new Alphabet[alphabets.length];
this.alphabets = alphabets;
count(digit);
change(digit);
category(digit);
}

//统计频率
public void count(int digit){
for (int i = 0; i < alphabets.length; i++) {
count[alphabets[i].toChar(digit) + 1]++;
}
}

//转换起始索引
public void change(int digit){
for (int i = 0; i < count.length - 1; i++) {
count[i+1] += count[i];
}
}

//重新分类
public void category(int digit){
for (int i = 0; i < alphabets.length; i++) {
int index = alphabets[i].toChar(digit);
int tempIndex = count[index];//此时为起始索引
count[index]++;
temp[tempIndex] = alphabets[i];//把原来字符串的值按起始索引重新组合
}

for (int i = 0; i < temp.length; i++) {
alphabets[i] = temp[i];
}
}

public Alphabet[] getAlphabets() {
return alphabets;
}

}
``````

### 2：等长字符串——低位优先排序

``````/**
* 低位优先
*/
public class LittleEndianSort {

private KeyIndexSort keyIndexSort;

private Alphabet[] alphabets;

public LittleEndianSort(Alphabet[] alphabets){
this.alphabets = alphabets;
}

public void sort(){
int indexLen = alphabets[0].getIndexLen();
int len = alphabets[0].R();
//从低位数开始排序
for (int i = len - 1; i >= 0 ; i--) {
keyIndexSort = new KeyIndexSort(alphabets,i,indexLen);
alphabets = keyIndexSort.getAlphabets();
}
}

public void show(){
for (int i = 0; i < alphabets.length; i++) {
System.out.printf("%s ",alphabets[i]);
}
}
}
``````

### 3：变长字符串——高位优先排序

• 在时间上：在比较小字符串时可以通过插入排序比较
• 在空间上：如果相同前缀的字符串过多，将会耗费大量空间
``````/**
* 高位优先
*/
public class HighEndianSort {
private KeyIndexSort keyIndexSort;

private Alphabet[] alphabets;

public HighEndianSort(Alphabet[] alphabets){
this.alphabets = alphabets;
}

public void sort(){
int indexLen = alphabets[0].getIndexLen();
if (indexLen >= Character.MAX_VALUE)
indexLen = Byte.MAX_VALUE;
//开始排序
keyIndexSort = new KeyIndexSort(alphabets,0,indexLen,0,alphabets.length - 1);
}

public void show(){
for (int i = 0; i < alphabets.length; i++) {
System.out.printf("%s ",alphabets[i]);
}
}

``````
``````
public class KeyIndexSort {

//字符串
private Alphabet[] alphabets;
//记录键的频率
private int[] count;
//临时数组
private Alphabet[] temp;

//低位排序
public KeyIndexSort(Alphabet[] alphabets,int digit,int indexLen){
count = new int[indexLen + 1];
temp = new Alphabet[alphabets.length];
this.alphabets = alphabets;
count(digit);
change();
category(digit);
}

//高位排序
public KeyIndexSort(Alphabet[] alphabets,int digit,int indexLen,int low,int high){
this.alphabets = alphabets;
if (high <= low)
return;
//        if (high - low <= 15)
//            InSertSort.insert_sort(alphabets);
count = new int[indexLen + 2];//空出一位处理变长的字符串
temp = new Alphabet[alphabets.length];
count(digit,low,high);
change();
category(digit,low,high);
//从左到右排序，在以首位字符开头的字符串数组内排序
for (int i = 0; i < indexLen; i++) {
this.alphabets = new KeyIndexSort(this.alphabets,digit+1,indexLen,low+count[i],low+count[i+1]-1).getAlphabets();
}
}

//统计频率
public void count(int digit){
for (int i = 0; i < alphabets.length; i++) {
count[alphabets[i].toChar(digit) + 1]++;
}
}

public void count(int digit,int low,int high){
for (int i = low; i <= high; i++) {
if (alphabets[i].toChar(digit) == Character.MAX_VALUE)
count[1] ++;    //表示长度为digit的字符串已经结束，记录长度为digit的字符串的数量
else
count[alphabets[i].toChar(digit) + 2]++;
}
}

//转换起始索引
public void change(){
for (int i = 0; i < count.length - 1; i++) {
count[i+1] += count[i];
}
}

//重新分类
public void category(int digit){
for (int i = 0; i < alphabets.length; i++) {
int index = alphabets[i].toChar(digit);
int tempIndex = count[index];//此时为起始索引
count[index]++;

temp[tempIndex] = alphabets[i];//把原来字符串的值按起始索引重新组合
}

for (int i = 0; i < temp.length; i++) {
alphabets[i] = temp[i];
}
}

//重新分类，分类后count记录为结束索引+1
public void category(int digit,int low,int high){
for (int i = low; i <= high; i++) {
int index = alphabets[i].toChar(digit);
int tempIndex;
if (index == Character.MAX_VALUE) {
tempIndex = count[0];
count[0]++; //长度为digit的字符串个数
}
else {
tempIndex = count[index + 1];
count[index+1]++;
}
temp[tempIndex] = alphabets[i];//把原来字符串的值按起始索引重新组合
}

for (int i = low; i <= high; i++) {
alphabets[i] = temp[i];
}
}

public Alphabet[] getAlphabets() {
return alphabets;
}

}
``````
``````//字符串插入排序
public static Alphabet[] insert_sort(Alphabet[] arr, int low, int high, int digit){
for (int i = low + 1; i <= high; i++) {
if(arr[i].toString().substring(digit).compareTo(arr[i-1].toString().substring(digit)) < 0)               //比前面的数小
{
Alphabet temp = arr[i];
int j;
for (j = i; j > low && temp.toString().substring(digit).compareTo(arr[j-1].toString().substring(digit)) < 0; j--)   //找到合适的位置
arr[j] = arr[j-1];
arr[j] = temp;          //把数字插进去
}
}
return arr;
}
``````

### 4：改善的快速排序：三向快速排序

``````
/**
* 三向快速排序
*/
public class Quick3Sort {

private Alphabet[] alphabets;

public Quick3Sort(Alphabet[] arr){
alphabets = arr;
quick_sort(alphabets,0,arr.length - 1,0);
}

//快速排序
public static Alphabet[] quick_sort(Alphabet[] arr, int low, int high, int digit){
//        if(high <= low + 10)    //对于小数组，使用插入排序
//        {
//            InSertSort.insert_sort(arr,low,high,digit);
//            return arr;
//        }
if (high <= low)
return arr;
int key;    //记录当前的切分字符
if (arr[low].toChar(digit) == Character.MAX_VALUE)
key = -1;
else
key = arr[low].toChar(digit);

int[] pivot = find_pivot(arr,low,high,key,digit);       //获取基准值的下标，便于分割数组
quick_sort(arr,low,pivot[0]-1,digit);   //左边排序
if (key >= 0)
quick_sort(arr,pivot[0],pivot[1],digit+1);  //中间排序，忽略首字符
quick_sort(arr,pivot[1]+1,high,digit);  //右边排序
return arr;
}

//找基准值的下标
public static int[] find_pivot(Alphabet[] arr,int low,int high,int key,int digit){
int i = low+1;
while (i <= high)
{
//记录当前下标的字符
int temp = arr[i].toChar(digit);
if (temp == Character.MAX_VALUE)
temp = -1;
//当前字符和切分的字符进行比较,保证左边比较小，右边比较大
if (temp < key)
SortUtil.swap(low++,i++,arr);
else if (temp > key)
SortUtil.swap(i,high--,arr);
else
i++;
}
int[] pivot = {low,high};
return pivot;
}

public void show(){
for (int i = 0; i < alphabets.length; i++) {
System.out.printf("%s ",alphabets[i]);
}
}
}
``````

## 二：字符串的查找

### 1：单词查找树

• 适用于字母表和键较小的情况，需要耗费空间
``````   //获取键值
public T get(String key){
StringTreeNode<T> node = get(key,root,0);
if (node == null)
return null;
return node.getValue();
}

private StringTreeNode<T> get(String key,StringTreeNode<T> node,int digit){
//在串的范围内查找
if (node == null)
return null;
if (digit == key.length())
return node;
int index = alphabet.toIndex(key.charAt(digit));
return  get(key,node.getNodes()[index],digit+1);//在对应的子树查找
}
}
``````

### 2：三向查找树

`````` //获取键值
public T get(String key){
ThreeStringTreeNode<T> node = get(key,root,0);
if (node == null)
return null;
return node.getValue();
}

public ThreeStringTreeNode<T> get(String key,ThreeStringTreeNode<T> node,int digit){
if (key.length() == 0)
return root;
if (node == null)
return null;
int index = alphabet.toIndex(key.charAt(digit));
//对三个子节点进行比较
if (alphabet.toChar(index) < node.getKey())
return get(key, node.getLeft(), digit);
else if (alphabet.toChar(index) > node.getKey())
return get(key, node.getRight(), digit);
//如果是中间结点，看看是不是够长度了
else if (digit < key.length() - 1)
return get(key, node.getMid(), digit+1);
else
return node;
}
``````

## 三：字符串的查找

### 1：暴力查找

``````
public int search(String pattern){
int index = -1;
int pLen = pattern.length();
//i指向文本的开头
for (int i = 0; i < len; i++) {
index = i;
//j指向模式串的开头
for (int j = 0; j < pLen; j++) {
if (text.charAt(i + j) != pattern.charAt(j))
{
index = -1;
break;
}
}
if (index != -1)
return index;
}
return index;
}

public int searchClear(String pattern){
int index = -1;
int pLen = pattern.length();
int i,j;
//i指向文本的末尾,j指向模式串的开头
for (i = 0,j = 0; i < len && j <pLen; i++) {
if (text.charAt(i) != pattern.charAt(j)){
i -= j;
j = 0;
}
else
j++;
}
if (j == pLen)
return i - pLen;
return index;
}
``````

### 2：KMP算法

#### DFA：有限状态转换机——dfa[文本字符][模式字符]

##### 转换过程：
1. 从文本前进，检查字符（ i+1）
2. 对于文本的字符，检查转换机。
• 如果模式的字符匹配，则转换机向右移动（j+1）
• 如果模式的字符不匹配，则转换机向左移动
（j = dfa[text.charAt(i)][pattern.charAt(j)]）
1. 如果转换机到达停止状态，则说明匹配了模式字符串
2. 如果在文本结束后还未到达停止状态，则没有匹配
##### 构造：扫描模式字符串

###### 例子：ABABC
• 状态0：A-[开始状态]                ——> 开始右移

• 状态1：A B-[0] [0]                    ——> 没有相同的，重新开始匹配

• 状态2：A B-[0] A-[0] [1]            ——>与状态0相同，从状态1继续匹配

• 状态3：A B-[0] A-[0] B-[1] [2]    ——>与状态1相同，从状态2继续匹配

• 状态5：A B-[0] A-[0] B-[1] C-[停止状态]——>匹配结束

``````    //创建状态机
public void createDFA(String pattern){
dfa = new int[alphabet.R()][pattern.length()];

//忽略模式串的首位，状态机启动
dfa[pattern.charAt(0)][0] = 1;

for (int lastIndex = 0,nextIndex = 1; nextIndex < pattern.length(); nextIndex++) {

//若匹配失败，状态机返回上一状态
for (int alphabetIndex = 0; alphabetIndex < alphabet.R(); alphabetIndex++) {
dfa[alphabetIndex][nextIndex] = dfa[alphabetIndex][lastIndex];
}

//匹配成功，状态机向前继续走 2,3,4....
dfa[pattern.charAt(nextIndex)][nextIndex] = nextIndex + 1;

//记录状态机的上一状态
lastIndex = dfa[pattern.charAt(nextIndex)][lastIndex];
}
}

``````

#### 查找

``````public int search(String pattern){
createDFA(pattern);
int index = -1;
int pLen = pattern.length();
int i,j;
//i指向文本的末尾,j指向模式串的开头
for (i = 0,j = 0; i < len && j <pLen; i++) {
j = dfa[text.charAt(i)][j];
}
if (j == pLen)
index = i - pLen;
return index;
}

``````

### 2：Boyer-Moore算法

• 字符不被包含：应该检查文本的下一位，将模式字符串与已知模式字符串对齐。否则字符会重叠
• 字符被包含：使用right数组（记录字符最右位置），检查文本的第x位，将模式字符串与字符出现的最右位置对齐。否则该字符会与更右边的字符重叠
• 保证字符向右移动
``````   public void initialize(String pattern){
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
right[c] = i;
}
}

public int search(String pattern){
initialize(pattern);
int index = -1;
int pLen = pattern.length();
int skip;
//i指向文本的开头,j指向模式串的末尾
for (int i = 0; i <= len - pLen; i += skip) {
skip = 0;
for (int j = pLen - 1; j >= 0; j--) {
char c = text.charAt(i + j);
if (c != pattern.charAt(j)){
//对齐模式字符串，模式串必须向前移动，避免重叠
skip = j - right[c];
if (skip <= 0)
skip = 1;
break;
}
}
//匹配
if (skip == 0)
return i;
}
return index;
}

``````

### 3：Rabin-Karp算法

1. 计算模式字符串的散列值
2. 计算文本中与模式字符串相同长度的子字符串的散列值，如果散列值和模式字符串相同再验证是否匹配。

• 除留取余法：适用于5位以内的数值
• Hornor：适用于多位数值，但是成本高
``````     for (int i = 0; i < target.length(); i++) {
hash = (R * hash + target.charAt(i)) % prime;
}
``````
• 高效Hornor：[i~M-1]的字符串的值=它减去第一个数，乘R，加上最后一个数。
`````` textHash = hash(text,pLen);
textHash = (textHash + Q - RM * text.charAt(i - pLen) % Q) % Q;
textHash = (textHash * R + text.charAt(i)) % Q;
``````

#### 查找

``````	private boolean check(int i) {
return true;
}

private boolean check(int i,String pattern) {
for (int j = 0; j < pattern.length(); j++)
if (pattern.charAt(j) != text.charAt(i + j))
return false;
return true;
}

//获得第M-1个R 余 Q
private void initialRM(String pattern){
int pLen = pattern.length();
this.RM = 1;
for (int i = 0; i < pLen; i++) {
RM = (R * RM) % Q;
}
}

//hornor哈希
private long hash(String key, int M) {
long h = 0;
for (int j = 0; j < M; j++)
h = (R * h + key.charAt(j)) % Q;
return h;
}

public int search(String pattern){
initialRM(pattern);
int pLen = pattern.length();
this.textHash = hash(text,pLen);
long pHash = hash(pattern,pLen);
//一开始就命中
if (pHash == textHash && check(0))
return 0;
for (int i = pLen; i < len; i++) {
//减去第一个数
textHash = (textHash + Q - RM * text.charAt(i - pLen) % Q) % Q;
//加上最后一个数
textHash = (textHash * R + text.charAt(i)) % Q;
if (pHash == textHash)
if (check(i - pLen + 1))
return i - pLen + 1;
}
return -1;
}
``````

## 四：字符串的压缩

### 1：X位编码

• 2种字母（正负对错）：单位编码（0,1）
• 4种字母（基因碱基）：双位编码（00,01,10,11）
• 8种字母（八仙过海）：三位编码（000,001,010,011,100,101,110,111）
• 2N种字母：log2N位编码

#### （1）压缩

1. 确定字符串的字符数量，方便解码顺利进行
2. 根据字符在字母表的索引和编码位数，压缩为01编码

#### （2）解压缩

1. 根据编码位数，读取字符的索引
2. 根据字母表和字符索引，解压缩对应字符

### 2：游程编码

#### （1）压缩

1. 从0开始，读取0或1，并记录个数
2. 若个数超过范围，则压缩个数，再压缩0，方便继续读取该字符
3. 若字符不同，则压缩个数

### 3：霍夫曼编码

#### （1）构造二叉树

1. 读取字符串，获取N个包含字符及其出现的频率的树，加入优先队列
`````` //统计频率
public void initFrequence(){
for (int i = 0; i < input.length; i++)
frequences[input[i]]++;
}
``````
1. 不断取两个最小的树，生成一个新的父节点和一棵新的树，树的根节点的频率为他们的和。
2. 只剩下一个结点时，为单词查找树的根节点。
``````   //创建树
public void createTree(){
//初始化多个树
for (int i = 0; i < input.length; i++) {
int freq = frequences[input[i]];
if (freq == 0 ) //忽略无关的字符
continue;
HuffmanNode node = new HuffmanNode(input[i],freq);
pq.insert(node);
}
//开始创建
while (pq.getSize() > 1){
HuffmanNode left = pq.delMaximum();
HuffmanNode right = pq.delMaximum();
HuffmanNode parent = new HuffmanNode('\0',left.getFrequence()+right.getFrequence(),left,right);
pq.insert(parent);
}
root = pq.delMaximum();
}
``````

#### （2）构建映射表

`````` //建立映射
private void codeTree(HuffmanNode node,String s){
if (node.isLeaf())
{
int index = alphabet.toIndex(node.getValue());
codes[index] = s;
return;
}
codeTree(node.getLeft(),s+'0');
codeTree(node.getRight(),s+'1');
}
``````

#### （3）传输树

1. 对树进行前序遍历，遇到内部结点输出0，遇到叶子结点输出1并且将其字符的ASCALL八码编码输出。
``````    //输出树
private void transferTree(HuffmanNode node){
if (node.isLeaf())
{
treeCode += "1";
int index = alphabet.toIndex(node.getValue());
treeCode += codes[index];
return;
}
treeCode += "0";
transferTree(node.getLeft());
transferTree(node.getRight());
}
``````
1. 遍历字符串，遇到1则构造叶子节点并加入字符，遇到0则构造父节点并递归构造左右子树。
``````   //解码树
private HuffmanNode decodeTree() {
if (index == input.length)
return null;
if (input[index] == '1') {
return new HuffmanNode(input[index], 0, null, null);
} else {
index++;
HuffmanNode left = decodeTree();
index++;
HuffmanNode right = decodeTree();
return new HuffmanNode('\0', 0, left, right);
}
}
``````

#### （3）压缩

1. 构造编码单词查找树
2. 根据树构造映射表
3. 根据映射表编码
`````` //根据明文,映射表压缩
public String compress(){
StringBuffer output = new StringBuffer();
for (int i = 0; i < input.length; i++) {
int index = alphabet.toIndex(input[i]);
String code = codes[index];
for (int j = 0; j < code.length(); j++) {
if (code.charAt(j) == '1')
output.append('1');
else
output.append('0');
}
}
return output.toString();
}

``````

#### （4）解压缩

1. 读取一棵树
2. 使用该树解码：从根节点开始，遇到0向左移动，遇到1向右移动，遇到叶子节点则获得字符。
`````` //根据密文解压缩
public String expand() {
StringBuffer output = new StringBuffer();
HuffmanNode node = root;
for (int i = 0; i < input.length;) {
while (!node.isLeaf()){
if (input[i++] == '0')
node = node.getLeft();
else
node = node.getRight();
}
output.append(node.getValue());
node =root;
}
return output.toString();
}
``````

### 4：LZW编码

• 00-7F：符号表中的128个单字符的键（最前补0）
• 80：保留80，作为文本结束的标志
• 81-FF：将其他的编码值分配给子字符串

#### （1）压缩

1. 找出输入的字符串在符号表中的最长前缀，并输出前缀的编码。
2. 继续查找下一字符，将前缀的编码和该字符的组合作为字母表的新建，扩展编码递增。
``````public static String compress(){
StringBuffer result = new StringBuffer();//编码结果
int code = EOF;  //用80标志文件结束
while (input.length() > 0){
//获取最长前缀，输出编码
String prefix = tree.longestPrefix(input);
result.append(tree.get(prefix));
//如果文本还未结束并且在字母表范围中，继续查找下一字符组合，扩展编码递增
if (prefix.length() < input.length() && code < sum)
tree.put(input.substring(0,prefix.length()+1),code++);
input = input.substring(prefix.length());
}
result.append(R);   //输出结束标记
return result.toString();
}
``````

#### （2）解压缩

1. 根据编码X，在符号表中找到和X匹配的字符串S1，再读取下一个编码获取S2
2. 将符号表的下一个扩展值递增，键设为S1+S2的首字母
`````` //构造逆表
public static String[] initST(){
String[] st = new String[sum];
//初始化字母表对应的映射
for (int i = 0; i < R; i++)
st[i] = ""+(char)i;
st[EOF] = " ";  //结束标记
return st;
}

public static String expand(){
StringBuffer result = new StringBuffer();//编码结果
String[] st = initST();
int found = EOF+1;  //记录扩展编码

//从密文第一个编码开始，根据逆表获取字符
int index = alphabet.toChar(input.charAt(0));
String value = st[index];

for (int i = 1; i < input.length(); i+=width) {
//输出编码对应字符串
result.append(value);
//获取下一个编码
index = alphabet.toChar(input.charAt(i));
//超出字母表范围
if (index == R)
break;
String temp = st[index];
//如果前缀码不可用，则根据上一个字符串的首字母获取新的前缀码
if (index == found)
temp = value + value.charAt(0);
//在编码范围之内，则扩展编码，添加新的键值对
if (found < sum)
st[found++] = value + temp.charAt(0);
value = temp;
}
return result.toString();
}
``````

0条评论