数据结构 二分查找及应用-板子篇

前言

重点在于一些板子比如有序表的编写、两种二分查找思路和效率分析。说起来不是什么特别难理解的东西,但是后面的效率分析光看书还真有点绕。设计算法的时候注意点别写错或者死循环吧。

模板

比较参数

  • 键(key)
    包含在记录中的,用于检索的关键字
  • 记录(record)
    包含键和其他信息(other)。
  • 转化和比较
    定义运算符支持记录到键的转换;重载运算符使记录之间的相互比较转化为键的比较。
typedef string Target;
//Key
class Key {
	Target key;
public:
	Key (Target x);
	Target the_key( ) const;
};
bool operator == (const Key &x, const Key &y);
bool operator < (const Key &x, const Key &y);
bool operator > (const Key &x, const Key &y);

Key::Key(Target x){

	key=x;

}

Target Key::the_key() const{
	return key;
}

bool operator == (const Key &x, const Key &y)
{
	return x.the_key( ) == y.the_key( );
}

bool operator > (const Key &x, const Key &y)
{
	return x.the_key( ) > y.the_key( );
}

bool operator < (const Key &x, const Key &y)
{
	return x.the_key( ) < y.the_key( );
}

//Record
class Record{
public:
	operator Key( ); // implicit conversion from Record to Key .
	Record(Target x="0", Target y="0");
	Target the_key() const;
	Target the_other() const;
private:
	Target key;
	Target other;
};
bool operator > (const Record &x, const Record &y);
bool operator < (const Record &x, const Record &y);
ostream & operator << (ostream &output, Record &x);

Record::Record(Target x, Target y){
	key=x;
	other=y;
}

Record::operator Key( ){
	Key tmp(key);
	return tmp;
}

Target Record::the_key() const{
	return key;
}

Target Record::the_other() const{
	return other;
}

bool operator > (const Record &x, const Record &y)
{
	return x.the_key( ) > y.the_key( );
}

bool operator < (const Record &x, const Record &y)
{
	return x.the_key( ) < y.the_key( );
}

ostream & operator << (ostream &output, Record &x)
{
	output<<x.the_key();
	output<<"  ";
	return output;
}

有序表

二分查找要求表中的键是标量或是其他有序类型,且这个表是完全有序的,由此需要引进有序表:

An ordered list is a list in which each entry contains a key, such that the keys are in order. That is, if entry i comes before entry j in the list, then the key of entry i is less than or equal to the key of entry j.

即有序表中的键是按照升序放置的。
而原先List的操作对它不适用的只有插入(insert)和置换(replace)。我们需要对它们进行重载,并且通过作用域解析去应用原来的List中的插入操作。
这里整了一手类模板和类派生,不做深入。

List

//List 
const int max_list = 30;
template <class List_entry>
class List {
public:
	List( );
	int size( ) const;
	bool full( ) const;
	bool empty( ) const;
	void clear( );
	void traverse(void (*visit)(List_entry &));
	Error_code retrieve(int position, List_entry &x) const;
	Error_code replace(int position, const List_entry &x);
	Error_code remove(int position, List_entry &x);
	Error_code insert(int position, const List_entry &x);
protected:
	int count;
	List_entry entry[max_list];
};

template <class List_entry>
List<List_entry> :: List( ) 
{
	count = 0;
}

template <class List_entry>
int List<List_entry> :: size( ) const
{
	return count;
}

template <class List_entry>
bool List<List_entry> :: full( ) const
{
	return(count==max_list);
}

template <class List_entry>
bool List<List_entry> :: empty( ) const
{
   return count == 0;
}

template <class List_entry>
void List<List_entry> :: clear( ) 
{
   count = 0;
}

template <class List_entry>
Error_code List<List_entry> :: insert(int position, const List_entry &x)
{
	if (full( ))return overflow;
	if (position < 0 || position > count) return Range_error;
	for (int i = count - 1; i >= position; i--)	entry[i + 1] = entry[i];
	entry[position] = x;
	count++;
	return success;
}

template <class List_entry>
Error_code List<List_entry> :: remove(int position, List_entry &x)
{
	if (empty( ))return underflow;
	if (position < 0 || position >= count)return Range_error;
	x = entry[position];
	for (int i = position; i < count-1; i++)entry[i] = entry[i+1];
	count--;
	return success;
}

template <class List_entry>
Error_code List<List_entry> :: replace(int position, const List_entry &x)
{
	if (position < 0 || position >= count)return Range_error;
	entry[position] = x;
	return success;
}

template <class List_entry>
Error_code List<List_entry> :: retrieve(int position, List_entry &x) const
{
	if (position < 0 || position >= count)return Range_error;
	x = entry[position];
	return success;
}

template <class List_entry>
void List<List_entry> :: traverse(void (*visit)(List_entry &))
{
	for (int i = 0; i < count; i++)(*visit)(entry[i]);
}

Ordered_list

//Ordered_list
class Ordered_list: public List<Record>{
public:
	Error_code insert(const Record &data);
	Error_code insert(int position, const Record &data);
	Error_code replace(int position, const Record &data);
};

Error_code Ordered_list :: insert(const Record &data)
{
	int s = size( );
	int position;
	for (position = 0; position < s; position++) {
		Record list_data;
		retrieve(position, list_data);
		if (data < list_data) break;
	}
	return List<Record> :: insert(position, data);
}

Error_code Ordered_list :: insert(int position, const Record &data)
{
	Record list_data;
	if (position > 0) {
		retrieve(position - 1, list_data);
		if (data < list_data)
			return fail;
	}	
	if (position < size( )) {
		retrieve(position, list_data);
		if (data > list_data)
			return fail;
	}
	return List<Record> :: insert(position, data);
}

Error_code Ordered_list :: replace(int position, const Record &data)
{
	if (position < 0 || position >= count)return Range_error;
	Record list_data;
	if (position > 0) {
		retrieve(position - 1, list_data);
		if (data < list_data)
			return fail;
	}	
	if (position < size( )) {
		retrieve(position, list_data);
		if (data > list_data)
			return fail;
	}
	entry[position] = data;
	return success;
}

二分查找

方法

首先将目标键与有序表中央的键进行比较,然后根据目标键是在中央键的前面还是后面,将注意力仅仅限制在表的第一部分或第二部分。在每一步都继续这一操作,则每次都可以将要查找的表长减少到一半。

设计

  • 使用两个下标 top 和 bottom 围住表中我们正在寻找目标键的那一部分。
  • 假定目标键在表中出现,则目标键将在下标 bottom 和 top 之间的范围被找到。
  • 首先将 bottom 置为0,top 置为the_list.size( ) - 1,计算二者的中间位置mid: m i d = ( b o t t o m + t o p ) / 2 mid=(bottom+top)/2
  • 接下来将目标键与 mid 位置上的键比较,然后修改 top 或者 bottom 中适当的一个,将表减少到 bottom 或者 top 所在的一半。
  • 但是必须保证剩下待查找的表项数 t o p b o t t o m + 1 top-bottom+1 在每次重复后都会绝对地减少。
  • 终止:当表的剩余部分至多包含一项,即 t o p b o t t o m top\leqslant bottom 时终止。

健忘版(Forgetful Version)

不管是否找到目标,查找算法都会对表再分,直到剩下的表长为1。
用下图来描述表到子表的分割:
在这里插入图片描述
如果表中目标出现超过一次,则当表的中间部分长度减为1并且命中目标时,它一定是第一个出现的目标。

利用递归形式编写:

Error_code recursive_binary_1(const Ordered_list &the_list, const Key &target, int bottom, int top, int &position)
{ 
	Record data;
	if (bottom < top) { // List has more than one entry.
		int mid = (bottom + top)/2;
		the_list.retrieve(mid, data);
		if (target > data) // Reduce to top half of list.
			return recursive_binary_1(the_list, target, mid + 1, top, position);
		else // Reduce to bottom half of list.
			return recursive_binary_1(the_list, target,	bottom, mid, position);
	}
	else { // List has exactly one entry.
		position = bottom;
		the_list.retrieve(bottom, data);
		if (data == target) return success;
		else return not_present;
	}
}

注意到递归形式为尾递归,可以将其转换为循环。

Error_code binary_search_1 (const Ordered_list &the_list, const Key &target, int &position)
{
	Record data;
	int bottom = 0, top = the_list.size( ) - 1;
	while (bottom < top) {
		int mid = (bottom + top)/2;
		the_list.retrieve(mid, data);
		if (data < target)
			bottom = mid + 1;
		else
			top = mid;
	}
	if (top < bottom) return not_present;
	else {
		position = bottom;
		the_list.retrieve(bottom, data);
		if (data == target) return success;
		else return not_present;
	} 
}

识别相等(Recognizing Equality)

使用一个变量以在每个阶段进行检查,了解是否发现目标,从而节省计算机时间。
在这里插入图片描述
用这种方法,若目标在表中出现超过一次,算法会返回目标的任何一个实例。

递归写法:

Error_code recursive_binary_2(const Ordered_list &the_list, const Key &target, int bottom, int top, int &position)
{
	Record data;
	if (bottom <= top) {
		int mid = (bottom + top)/2;
		the_list.retrieve(mid, data);
		if (data == target) {
			position = mid;
			return success;
		}
		else if (data < target)
			return recursive_binary_2(the_list, target, mid + 1, top, position);
		else
			return recursive_binary_2(the_list, target, bottom, mid - 1, position);
	}
	else return not_present;
}

循环写法:

Error_code binary_search_2(const Ordered_list &the_list, const Key &target, int &position)
{
	Record data;
	int bottom = 0, top = the_list.size( ) - 1;
	while (bottom <= top) {
		position = (bottom + top)/2;
		the_list.retrieve(position, data);
		if (data == target) return success;
		if (data < target) bottom = position + 1;
		else top = position - 1;
	} 
	return not_present;
}

效率分析

  • 效率分析一般是指平均情况,最坏情况和最好情况。实际问题可能和理论分析的场景有一定区别,这个时候需要我们做合适的选择。
  • 在这里插入图片描述
  • binary_search_1 有更小的 l o g n logn 的系数,当 n n 足够大时,它会做较少的比较。
  • binary_search_2 的常数项更小,当 n n 较小时,它会做较少的比较。
  • 但是对于较小的 n n ,建立二分查找的系统开销和额外的程序设计强度使得使用它比使用顺序查找代价更大。
  • 在某些场景下(列表长度很长且要查找的元素是列表中出现较多的元素)recogniz可以做为特殊的优化。

猜你喜欢

转载自blog.csdn.net/qq_45401156/article/details/105908916