《编程珠玑》第二章提到了一个变位词问题,变位词指的是一个单词可以通过改变其他单词中字母的顺序来得到,也叫做兄弟单词,如army->mary。由变位词可以引申出几个算法问题,包括字符串包含问题,比较两个字符串是否是变位词,以及找出字典中变位词集合的问题。
字符串包含问题
(1) 问题描述:存在字符串1和字符串2,假设字符串2相对较短,如何快速地判定字符串2中的字符都存在于字符串1里(假定字符串只包含字母)?
李松
《编程珠玑》第二章提到了一个变位词问题,变位词指的是一个单词可以通过改变其他单词中字母的顺序来得到,也叫做兄弟单词,如army->mary。由变位词可以引申出几个算法问题,包括字符串包含问题,比较两个字符串是否是变位词,以及找出字典中变位词集合的问题。
(1) 问题描述:存在字符串1和字符串2,假设字符串2相对较短,如何快速地判定字符串2中的字符都存在于字符串1里(假定字符串只包含字母)?
《编程珠玑》第二章提到了n元一维向量旋转算法(又称数组循环移位算法)的五种思路,并且比较了它们在时间和空间性能上的区别和优劣。
将一个n元一维向量向左旋转i个位置。例如,假设n=8,i=3,向量abcdefgh旋转为向量defghabc。简单的代码使用一个n元的中间向量在n步内可完成该工作。你能否仅使用几十个额外字节的内存空间,在正比于n的时间内完成向量的旋转?
《编程珠玑》里面讲到了一种算法导论里没有提到过的位图排序方法,这种排序方法是通过牺牲空间效率来追求时间效率(线性时间)以达到时间-空间折中与双赢的目的。下面简单讲一下我对位图排序思想的理解。
1 . 输入:一个至多包含1千万个非负整数的文件
排序算法相信对大家来说都不陌生,或许很多人已经把它们记得滚瓜烂熟,随时可以写出来。是的,这些都是最基本的算法。很惭愧我没有达到那种熟练程度,甚至都快忘了。最近把各种内部排序算法复习了一下,包括插入排序(直接插入排序,折半插入排序,希尔排序)、交换排序(冒泡排序,快速排序)、选择排序(简单选择排序,堆排序)、2-路归并排序。(另:堆排序原理说起来比较长,请看我的另一篇文章:堆排序的算法实现)
由于堆排序算法说起来比较长,所以在这里单独讲一下。堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将L[n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。
堆的定义如下:n个关键字序列L[n]成为堆,当且仅当该序列满足:①L(i) <= L(2i)且L(i) <= L(2i+1) 或者 ②L(i) >= L(2i)且L(i) >= L(2i+1) 其中i属于[1, n/2]。
观察者模式(Observer):定义了对象之间的一对多关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
实现观察者模式的方法有多种,但是以包含Subject与Observer接口的类设计的做法最常见,下面看看观察者模式的类图:
单例模式(Singleton Pattern,也称为单件模式),使用最广泛的设计模式之一。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
定义一个单例类,私有化它的构造函数,以防止外界创建单例类的对象;使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
教学版,即懒汉版(Lazy Singleton):单例实例在第一次被使用时才进行初始化,这叫做延迟初始化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// version 1.0
class Singleton
{
private:
static Singleton* instance;
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton* getInstance() {
if(instance == NULL) {
instance = new Singleton();
}
return instance;
}
};
// init static member
Singleton* Singleton::instance = NULL;
Lazy Singleton存在内存泄露的问题,这里有两种解决方法:
对于第二种解决方法,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// version 1.1
class Singleton
{
private:
static Singleton* instance;
private:
Singleton() { };
~Singleton() { };
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
class Deletor {
public:
~Deletor() {
if(Singleton::instance != NULL)
delete Singleton::instance;
}
};
static Deletor deletor;
public:
static Singleton* getInstance() {
if(instance == NULL) {
instance = new Singleton();
}
return instance;
}
};
// init static member
Singleton* Singleton::instance = NULL;
在程序运行结束时,系统会调用静态成员deletor
的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
注意version 1.0
与version 1.1
都不是线程安全的,要使其线程安全,可以使用双检测锁模式(Double-Checked Locking Pattern):1
2
3
4
5
6
7
8
9static Singleton* getInstance() {
if(instance == NULL) {
Lock lock; // 基于作用域的加锁,超出作用域,自动调用析构函数解锁
if(instance == NULL) {
instance = new Singleton();
}
}
return instance;
}
在单例类内再定义一个嵌套类,总是感觉很麻烦,所以《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用函数内的 local static 对象。当第一次访问getInstance()
方法时才创建实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14// version 1.2
class Singleton
{
private:
Singleton() { };
~Singleton() { };
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
};
C++0X以后,要求编译器保证内部静态变量的线程安全性,故C++0x之后该实现是线程安全的,C++0x之前仍需加锁。
饿汉版(Eager Singleton):指单例实例在程序运行时被立即执行初始化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// version 1.3
class Singleton
{
private:
static Singleton instance;
private:
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance() {
return instance;
}
}
// initialize defaultly
Singleton Singleton::instance;
由于在main函数之前初始化,所以没有线程安全的问题。但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。
总结:
参考:
[1] 单例模式的Java实现,请看CoolShell的《深入浅出Singleton设计模式》
[2] www.zkt.name/dan-li-mo-shi-singleton-ji-c-shi-xian