C++ 数据结构算法 学习笔记(33) -查找算法及企业级应用
数组和索引
日常生活中,我们经常会在电话号码簿中查阅“某人”的电话号码,按姓查询或者按字母排 序查询;在字典中查阅“某个词”的读音和含义等等。在这里,“电话号码簿”和“字典”都可 看作一张查找表,而按“姓”或者“字母”查询则是按索引查询!
索引把线性表分成若干块,每一块中的元素存储顺序是任意的,但是块与块间必须按关键字 大小按顺序排列。即前一块中的最大关键字值小于后一块中的最小关键字值。 分块以后,为了快速定义块,还需要建立一个索引表,索引表中的一项对应于线性表中的一 块,索引项由键域和链域组成。键域存放相应关键字的键值,链域存放指向本块第一个节点和最 后一个节点的指针,索引表按关键字由小到大的顺序排列!
数组是特殊的块索引(一个块一个元素):
哈希表是非常经典的块索引!
分块查找的算法分两步进行,首先确定所查找的节点属于哪一块,即在索引表中查找其所在的块, 然后在块内查找待查询的数据。由于索引表是递增有序的,可采用二分查找,而块内元素是无序 的,只能采用顺序查找。(块内元素较少,则不会对执行速度有太大的影响
二分查找
二分查找法实质上是不断地将有序数据集进行对半分割,并检查每个分区的中间元素。再重 复根据中间数确定目标范围并递归实行对半分割,直到中间数等于待查找的值或是目标数不在搜 索范围之内!
#include <iostream>
#include <string>
using namespace std;
int int_compare(const void* key1, const void* key2)
{
const int* ch1 = (const int*) key1;
const int* ch2 = (const int*) key2;
return (*ch1 - *ch2);
}
int char_compare(const void* key1, const void* key2)
{
const char* ch1 = (const char*)key1;
const char* ch2 = (const char*)key2;
return (*ch1 - *ch2);
}
int BinarySearch(void* sorted, int len, int elemSize, void* search, int (*compare)(const void *key1, const void *key2))
{
int left = 0;
int right = 0;
int middle = 0;
left = 0;
right = len - 1;
while (left <= right)
{
int ret = 0;
middle = (left + right) / 2;
ret = compare(static_cast<char *>(sorted)+(elemSize*middle), search);
if (ret ==0)
{
return middle;
}
else if (ret >0)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return -1;
}
int main()
{
int arr[] = { 1,3,7,9,11 };
int search[] = {0,1,7,2,11,12};
for (int i = 0; i < sizeof(search) / sizeof(search[0]); i++)
{
int index = BinarySearch(arr, sizeof(arr) / sizeof(arr[0]),sizeof(int), & search[i], &int_compare);
cout << "The result is: " << index << endl;
}
char arr1[] = { 'a','c','d','f','j' };
char search1[] = {'0','a','e','j','z'};
cout << endl;
cout << "Char searching..." << endl;
for (int i = 0; i < sizeof(search1) / sizeof(search1[0]); i++)
{
int index = BinarySearch(arr1, sizeof(arr1) / sizeof(arr1[0]),sizeof(char), & search1[i], &char_compare);
cout << "The result is: " << index << endl;
}
system("pause");
return 0;
}
穷举搜索
有20枚硬币,可能包括4种类型:1元、5角、1角和5分。
已知20枚硬币的总价值为10元,求各种硬币的数量。 例如:4、11、5、0就是一种方案。
而8、2、10、0是另一个可能的方案,显然方案并不是 唯一的,请编写程序求出类似这样的不同的方案一共有多少种?
(1)编程思路。 直接对四种类型的硬币的个数进行穷举。其中,1元最多10枚、5角最多20枚、1角最多 20枚、5分最多20枚。
如果以元为单位,则5角、1角、5分会化成浮点型数据,容易计算出错。可以将1 元、5角、1角、5分变成100分、50分、10分和5分,从而全部采用整型数据处理。
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a100 = 0; //one dollar coin
int a50 = 0;
int a10 = 0;
int a5 = 0;
int cnt = 0;
for (a100 = 0; a100 <= 10; a100++)
{
for (a50 = 0; a50 <=(1000-a100*100)/50; a50++)
{
for (a10 = 0; a10 <= (1000-a100*100-a50*50)/10; a10++)
{
for (a5 = 0; a5 <= (1000 - a100 * 100 - a50 * 50 - a10*10) / 5; a5++)
{
if ((a100 * 100 + a50 * 50 + a10 * 10 + a5 * 5) == 1000 && (a100 + a50 + a10 + a5) == 20)
{
cout << a100 << "," << a50 << "," << a10 << "," << a5 << "," << endl;
cnt++;
}
}
}
}
}
cout << "The total method to have the $10 dollar with 20 coin is: " << cnt << endl;
system("pause");
return 0;
}
穷举法(枚举法)的基本思想是:列举出所有可能的情况,逐个判断有哪些是符合问题所要求 的条件,从而得到问题的全部解答。 它利用计算机运算速度快、精确度高的特点,对要解决问题的所有可能情况,一个不漏地进行检 查,从中找出符合要求的答案。
用穷举算法解决问题,通常可以从两个方面进行分析:
(1)问题所涉及的情况:问题所涉及的情况有哪些,情况的种数必须可以确定。把它描述 出来。应用穷举时对问题所涉及的有限种情形必须一一列举,既不能重复,也不能遗漏。重复列 举直接引发增解,影响解的准确性;而列举的遗漏可能导致问题解的遗漏。
(2)答案需要满足的条件:分析出来的这些情况,需要满足什么条件,才成为问题的答案。 把这些条件描述出来。
并行搜索
并发的基本概念
所谓并发是在同一实体上的多个事件同时发生。并发编程是指在在同一台计算机上“同时” 处理多个任务。
要理解并发编程,我们必须要理解如下一些基本概念: 计算机就像一座工厂,时刻在运行,为人类服务。它的核心是CPU,它承担了所有的计算任 务,就像工厂的一个现场指挥官。
进程就像工厂里的车间,承担“工厂”里的各项具体的“生产任务”,通常每个进程对应一 个在运行中的执行程序,比如,QQ和微信运行的时候,他们分别是不同的进程。
因为特殊原因,现场指挥官人才短缺,整个工厂只有一个指挥官,一次只能指导一个车间生 产,而所有的车间都必须要有现场指挥官在场才能生产。也就是说,一个车间开工的时候, 其他车间都必须停工。
背后的含义:任一时刻,单个CPU一次只能运行一个进程,此时其他进程处于非运行状态。
一个车间(进程)可以包括多条生产线,线程就好比车间(进程)里的生产线。所有生产线 (设备和人)都属于同一车间的资源,受车间统一调度和调配,并共享车间所有资源(如空 间或洗手间)。
背后的含义:一个进程可以拥有多个线程,每个线程可以可以独立并行执行,多个线程共 享同一进程的资源,受进程管理。
理解了以上这些概念后,我们接下来再继续讲解并行搜索的概念: 假设我们要从很大的一个无序的数据集中进行搜索,
假设我们的机器可以一次性容纳这么多 数据。从理论上讲,对于无序数据,如果不考虑排序,已经很难从算法层面优化了。而利用 上面我们提到的并行处理思想,我们可以很轻松地将检索效率提升多倍。具体实现思路如下: 将数据分成N个块,每个块由一个线程来并行搜索。
#include <iostream>
#include <string>
#include <Windows.h>
#include <time.h>
using namespace std;
#define TEST_SIZE (1024*1024*200)
#define NUMBER 20
typedef struct _search
{
int* data;
size_t start;
size_t end;
size_t count;
}search;
//int main1()
//{
// int* data = NULL;
// int count = 0;
//
// data = new int[TEST_SIZE];
//
// for (int i = 0; i < TEST_SIZE; i++)
// {
// data[i] = i;
// }
//
// time_t start = 0, end = 0;
// time(&start);
//
// for (int j = 0; j < 10; j++)
// {
// for (int i = 0; i < TEST_SIZE; i++)
// {
// if (data[i] == NUMBER)
// {
// count++;
// }
// }
// }
//
// time(&end);
// cout << "The time used for the search function is: " << end - start << endl;
// system("pause");
// return 0;
//}
DWORD WINAPI ThreadProc(void* lpParam)
{
search* s = (search*)lpParam;
time_t start, end;
cout << "The new thread is executing..." << endl;
time(&start);
for (int j = 0; j < 10; j++)
{
for (size_t i = s->start; i < s->end; i++)
{
if (s->data[i] == NUMBER)
{
s->count++;
}
}
}
time(&end);
cout << "The required time to searching the data is: " << end - start << endl;
return 0;
}
int main()
{
int* data = NULL;
int count = 0;
int mid = 0;
search s1, s2;
data = new int[TEST_SIZE];
for (int i = 0; i < TEST_SIZE; i++)
{
data[i] = i;
}
mid = TEST_SIZE / 2;
s1.data = data;
s1.start = 0;
s1.end = mid;
s1.count = 0;
s2.data = data;
s2.start = mid + 1;
s2.end = TEST_SIZE -1;
s2.count = 0;
DWORD threadID1; //Thread 1 identity
HANDLE hThread1; //Thread 1 handle
DWORD threadID2; //Thread 2 identity
HANDLE hThread2; //Thread 2 handle
cout << "Creating the threads..." << endl;
hThread1 = CreateThread(NULL, 0, ThreadProc, &s1, 0, &threadID1); //Creating first thread
hThread2 = CreateThread(NULL, 0, ThreadProc, &s2, 0, &threadID2); // Creating second thread
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
cout << "The process is waiting the thread to return..." << endl;
cout << "The total count for the search value is: " << s1.count + s2.count << endl;
system("pause");
return 0;
}