本章内容:OOP思想的应用,具体代码输出
问题描述如下:
在使用OOP思想解决问题的时候,需要从具体的问题场景中。抽象出来哪些实体,在计算机上描述这个实体,那也就是类 。根据实体间的关系,也就得出了类与类之间的关系。
分析:迷宫行列,由人给出。所以说这个描述迷宫的二维数组是动态开辟出来的。迷宫整个地图可以看做是一个类(行、列、以及迷宫节点类型的二级指针:动态开辟二维数组)
迷宫是由一个个的具体位置节点组成,这每一个节点可以抽象成一个类(属性:横坐标、纵坐标、节点值、一个方向数组,表示这个节点四个方向的可走性);
接下来的问题就是:如何深度搜索一个迷宫路径?
答:如果使用非递归来实现深度搜索,那么肯定是需要一个栈了。如果当前节点可以走(其值为0),然后这个节点就可以放入到这个栈中了。然后开始查看栈顶元素,查看其4个方向的可走性,然后确定它是留在(保存最终的路径结果)栈里面,还是出栈(不可以作为路径的一个节点)。
这里再次分析问题,迷宫从左上角出发,如果能走到右下角,说明走通了(有路径)。否则在任何一节点卡死,则说明迷宫不通。这里使用的深度遍历,就是说 当前方向能走,则走下去。暂时不用管其他的方向,先沿着这个方向走下去(能走则一直走)。但是有一点需要注意:从当前节点走到下一个节点,那么这两个节点的方向应该改变为不能走。结束之后才真正把下个节点给入栈。
注:从左上角出发,为了尽可能快地到达右下角。迷宫的4个数组方向,规定方向的先后顺序为
1 → 2↓ 3← 4 ↑ 。
当行走过程中,发现走入了死路。(刚走过的路先被堵死,然后其他3个方向的路也走不了。那当然就是说这个节点 其实走不了,那于是就从栈里面删除这个节点,然后立即标注 刚才我走的这个路 是个死路,千万不可以再进去了。然后开始继续看原栈顶元素的其它方向了)。
最终源代码如下:
#include
#include
#include
#include
using namespace std;
//迷宫里面节点的那个方向数组的下标
const int RIGHT = 0;//右
const int DOWN = 1;//下
const int LEFT = 2;//左
const int UP = 3;//上
const int DirectNum = 4;//一个节点有4个方向
//迷宫节点
struct Node
{
int x;//节点坐标
int y;
int node_val;//节点值
int DirectArr[DirectNum];//节点方向数组 4个
};
/*
对于上面的那个方向数组 里面每个值:
YES 表示可以走
NO 表示不可以走
0 0 0 1 1
1 0 0 0 1
1 1 0 1 1
1 1 0 0 1
1 1 1 0 0
*/
const int YES = 5;//可以走
const int NO = 6;//不可以走
//迷宫类型
class Maze
{
public:
Maze(int Row, int Col)//外部初始化迷宫
{
if (Row < 1 || Col < 1)
throw"Init failed!!!";
_Row = Row;
_Col = Col;
_pMazeArray = new Node*[Row];
for (int i = 0; i < Row; ++i)
_pMazeArray[i] = new Node[Col];
}
// 可以初始化迷宫节点的基本信息
void initNode(int i,int j,int val)
{
_pMazeArray[i][j].x = i;
_pMazeArray[i][j].y = j;
_pMazeArray[i][j].node_val = val;
//初始化 这个节点的四个方向都是不能走
for (int k = 0; k < DirectNum; ++k)
_pMazeArray[i][j].DirectArr[k] = NO;
/*
那么以后再做的时候,只需要去调整值为0的节点的方向就行了
而且最妙的一点就在于:已经把这个边界的外围都封死了。
下面再调整节点方向状态的时候,不需要再去调整了
*/
}
void setNodeState()// 开始设置所有节点的四个方向的状态
{
for (int i = 0; i < _Row; ++i)
{
for (int j = 0; j < _Col; ++j)
{
if (_pMazeArray[i][j].node_val == 1)
{
continue;
//对于节点值为1的节点 没必要修改其原方向数组
}
//节点值为0的节点 可以走的节点
if (j < _Col - 1 && _pMazeArray[i][j + 1].node_val
== 0)//右边能走
{
_pMazeArray[i][j].DirectArr[RIGHT] = YES;
}
if ( i < _Row - 1 && _pMazeArray[i+1][j].node_val
== 0)//下边能走
{
_pMazeArray[i][j].DirectArr[DOWN] = YES;
}
//左边能走
if ( j >0 && _pMazeArray[i][j-1].node_val == 0)
{
_pMazeArray[i][j].DirectArr[LEFT] = YES;
}
//上边能走
if ( i >0 && _pMazeArray[i-1][j].node_val == 0)
{
_pMazeArray[i][j].DirectArr[UP] = YES;
}
}
}
}
void searchMazePath()// 开始从左上角搜索迷宫的路径信息了
{
if (_pMazeArray[0][0].node_val == 1)
return;//完蛋啊 入口堵死 进不去
_resultArrayStack.push(_pMazeArray[0][0]);//把【0】【0】入栈
while (!_resultArrayStack.empty())
{
Node CurTop = _resultArrayStack.top();
int x = CurTop.x;
int y = CurTop.y;
//先看看走到终点木有?
if (x == _Row - 1 && y == _Col - 1)
{
return;//找到了 不走了
}
/*$$$$$$$$$$$$$$$$$$$没有到,继续找$$$$$$$$$$$$$$$$$$*/
if (_pMazeArray[x][y].DirectArr[RIGHT] == YES)//先往右嘛
{
//走了就把路封起来
_pMazeArray[x][y].DirectArr[RIGHT] = NO;
_pMazeArray[x][y + 1].DirectArr[LEFT] = NO;
//把右边的入栈
_resultArrayStack.push(_pMazeArray[x][y + 1]);
continue;
}
//右边不行,往下
if (_pMazeArray[x][y].DirectArr[DOWN] == YES)
{
//走了就把路封起来
_pMazeArray[x][y].DirectArr[DOWN] = NO;
_pMazeArray[x + 1][y].DirectArr[UP] = NO;
//把右边的入栈
_resultArrayStack.push(_pMazeArray[x + 1][y]);
continue;
}
//下边不行,往左
if (_pMazeArray[x][y].DirectArr[LEFT] == YES)
{
//走了就把路封起来
_pMazeArray[x][y].DirectArr[LEFT] = NO;
_pMazeArray[x][y - 1].DirectArr[RIGHT] = NO;
_resultArrayStack.push(_pMazeArray[x][y - 1]);
//把右边的入栈
continue;
}
//左边不行,往上
if (_pMazeArray[x][y].DirectArr[UP] == YES)
{
//走了就把路封起来
_pMazeArray[x][y].DirectArr[UP] = NO;
_pMazeArray[x - 1][y].DirectArr[DOWN] = NO;
_resultArrayStack.push(_pMazeArray[x - 1][y]);
//把上边的入栈
continue;
}
/*$$$$$$$$$$$$$$$$$$$还要去走,说明到了死路了$$$$$$$$$$$$$$$$$$*/
_resultArrayStack.pop();//这个节点走不了
}
}
//上面的while 有两种可能:
//1 走不通,然后把栈所有节点退完了
//2 找到了,栈内是路径信息(反方向的)
void showMazePath()// 打印迷宫路径搜索的结果
{
if (_resultArrayStack.empty())
{
cout << "找遍了 没有这个路径" << endl;
return;
}
while (!_resultArrayStack.empty())//把节点的值 修改为*
{
Node CurTop = _resultArrayStack.top();
int x = CurTop.x;
int y = CurTop.y;
_pMazeArray[x][y].node_val = '*';
_resultArrayStack.pop();
}
for (int i = 0; i < _Row; ++i)
{
for (int j = 0; j < _Col; ++j)
{
if (_pMazeArray[i][j].node_val == '*')
cout << "*" << " ";
else cout << _pMazeArray[i][j].node_val << " ";
}
cout << endl;
}
}
private:
int _Row;
int _Col;
Node** _pMazeArray;
stack<Node>_resultArrayStack;//遍历中的栈
};
int main()
{
cout << "请输入迷宫的行列数(例如:10 10):";
int row, col, data;
cin >> row >> col;
Maze maze(row, col); // 创建迷宫对象
cout << "请输入迷宫的路径信息(0表示可以走,1表示不能走):" << endl;
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
cin >> data;
// 可以初始化迷宫节点的基本信息
maze.initNode(i, j, data);
}
}
// 开始设置所有节点的四个方向的状态
maze.setNodeState();
// 开始从左上角搜索迷宫的路径信息了
maze.searchMazePath();
cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << endl;
// 打印迷宫路径搜索的结果
maze.showMazePath();
return 0;
}
此次写代码,花费了大量时间在
先开始 这个地方写的是Node CurTop = _resultArrayStack.top();
里面的CurTop,后来又修改成_resultArrayStack.top() 还是运行不出来结果。调试了5个多小时,心塞啊
多简单的问题 唉 !!!!!!!!!!!!
上面的深度优先,是走下去则沿着一个方向(规定好的 右 下 左 上),直至走出迷宫或者死胡同。当站在这个节点上,如果刚才规定好的那个方向可以走 则就按照那个方向前进。并不会去判断一下哪条路会近一些?例如 :上面的代码走下面的迷宫:
这样走,明显不是最优解,绕了很大的胡同。
当然只是简单的更换行走方向顺序,肯定也是不合适的。
这里就要使用到广度优先遍历 来找到迷宫路径的最短路径。
提高情况下:深度优先遍历采用的是递归 当然我们这里使用了栈实现了非递归版本。而广度优先遍历采用的是 类似剥洋葱的方式,逐渐接近最终解。以达到取得最优解的结果。当然这里就非常自然地使用上了队列的结构。
广度优先遍历的方式分析如下:从入口位置进入,把门口节点入队列。然后类似于 深度优先遍历,进入循环控制结构:队列不为空,然后检查队头元素的 4个方向的是否可行。依次把可以走的下一个位置给入队,注意:这里是所有可以走的下一步节点是都可以入队的。(当然是跟在队尾了) 处理完这个队头节点之后(4个方向都处理了),然后这个队头节点既可以走了。(当然在处理下一个这个可以走的位置的时候,节点间的移动状态也需要去置为NO)继续处理下一个队头元素。
经过循环之后,重点肯定是可以找到最短路径的。也就是我们在到达终点的时候,随着之前的入队、出队,可这些元素无论是有效的还是无效的,我们都没有保留下来。根本不确定 到底哪个元素才是属于这个最短路径上的节点呢?(深度优先遍历的栈最终保存的就是其路径信息)。所以这里就需要额外的花上一部分空间,来保存最短路径上的节点(行走过程中的这个最短路径节点之间的位置信息)都是哪些节点?
如下:最坏的情况无非就是 每个位置都可以走。
简单来做就是:把上面的二维数组(迷宫地图)的节点 一一映射到这个一位数组之上。(其实在内存之上,没有所谓的二维。只是抽象意义上的二维)
映射方法:二维数组的坐标(x,y)——》一位数组的坐标(x*Row+y)。这也即:一维数组的下标就标识着 二维数组上的某一个节点(唯一的)。
于是这样就好做了:从这个节点A到达下一个节点B,应该在一位数组B下标的数据域里面记载上A节点的二维坐标。(下次从B找它是从哪来的? 或者说 B的前一个节点元素是谁? 就可以很方便地根据B下标里面的二维坐标找到A)。
这样从最终的终点下标 在这个辅助数组里面往前找,直到找到门口节点。就可以把这个路径的节点信息 一一还原出来(一条完整的路径就出来了),这个过程是从后向前逆推的。
源代码如下:
#include
#include
#include
#include
#include
using namespace std;
//迷宫里面节点的那个方向数组的下标
const int RIGHT = 0;//右
const int DOWN = 1;//下
const int LEFT = 2;//左
const int UP = 3;//上
const int DirectNum = 4;//一个节点有4个方向
//迷宫节点
struct Node
{
int x;//节点坐标
int y;
int node_val;//节点值
int DirectArr[DirectNum];//节点方向数组 4个
};
/*
对于上面的那个方向数组 里面每个值:
YES 表示可以走
NO 表示不可以走
0 0 1 1 1 1
1 0 0 0 0 1
1 0 1 1 0 1
1 0 0 0 0 1
1 0 1 1 1 1
1 0 0 0 0 0
*/
const int YES = 5;//可以走
const int NO = 6;//不可以走
//迷宫类型
class Maze
{
public:
Maze(int Row, int Col)//外部初始化迷宫
{
if (Row < 1 || Col < 1)
throw"Init failed!!!";
_Row = Row;
_Col = Col;
_pMazeArray = new Node*[Row];
for (int i = 0; i < Row; ++i)
_pMazeArray[i] = new Node[Col];
//给辅助数组开辟空间 按照最坏情况处理
_result_node_information.resize(Row * Col);
}
void initNode(int i,int j,int val)// 可以初始化迷宫节点的基本信息
{
_pMazeArray[i][j].x = i;
_pMazeArray[i][j].y = j;
_pMazeArray[i][j].node_val = val;
//初始化 这个节点的四个方向都是不能走
for (int k = 0; k < DirectNum; ++k)
_pMazeArray[i][j].DirectArr[k] = NO;
/*
那么以后再做的时候,只需要去调整值为0的节点的方向就行了
而且最妙的一点就在于:已经把这个边界的外围都封死了。
下面再调整节点方向状态的时候,不需要再去调整了
*/
}
void setNodeState()// 开始设置所有节点的四个方向的状态
{
for (int i = 0; i < _Row; ++i)
{
for (int j = 0; j < _Col; ++j)
{
if (_pMazeArray[i][j].node_val == 1)
{
continue;
//对于节点值为1的节点 没必要修改其原方向数组
}
//节点值为0的节点 可以走的节点
if (j < _Col - 1 && _pMazeArray[i][j + 1].node_val
== 0)//右边能走
{
_pMazeArray[i][j].DirectArr[RIGHT] = YES;
}
if ( i < _Row - 1 && _pMazeArray[i+1][j].node_val
== 0)//下边能走
{
_pMazeArray[i][j].DirectArr[DOWN] = YES;
}
//左边能走
if ( j >0 && _pMazeArray[i][j-1].node_val == 0)
{
_pMazeArray[i][j].DirectArr[LEFT] = YES;
}
//上边能走
if ( i >0 && _pMazeArray[i-1][j].node_val == 0)
{
_pMazeArray[i][j].DirectArr[UP] = YES;
}
}
}
}
void searchMazePath()// 开始从左上角搜索迷宫的路径信息了
{
if (_pMazeArray[0][0].node_val == 1)
return;//完蛋啊 入口堵死 进不去
_use_Queue_BFS.push(_pMazeArray[0][0]);//把【0】【0】入队列
while (!_use_Queue_BFS.empty())
{
//得到队头元素信息
Node CurTop = _use_Queue_BFS.front();
int x = CurTop.x;
int y = CurTop.y;
//下面是处理队头元素的四个方向的 看是否可以走
if (_pMazeArray[x][y].DirectArr[RIGHT] == YES)//先往右嘛
{
_pMazeArray[x][y].DirectArr[RIGHT] = NO;//走了就把路封起来
_pMazeArray[x][y + 1].DirectArr[LEFT] = NO;
//下面这句话是 记录当前节点的二维坐标到下一个节点(右节点)的一维数组中去
_result_node_information[x * _Row + y + 1]
= _pMazeArray[x][y];
//把右边的入队列
_use_Queue_BFS.push(_pMazeArray[x][y + 1]);
//先看看走到终点木有?
if (check_is_end(_pMazeArray[x][y+1]))
{
return;//找到了 不走了 也不需要把终点位置去记录给别人
}
}
if (_pMazeArray[x][y].DirectArr[DOWN] == YES)//右边不行,往下
{
_pMazeArray[x][y].DirectArr[DOWN] = NO;//走了就把路封起来
_pMazeArray[x + 1][y].DirectArr[UP] = NO;
//下面这句话是 记录当前节点的二维坐标到下一个节点(下节点)的一维数组中去
_result_node_information[(x + 1) * _Row + y]
= _pMazeArray[x][y];
//把下边的入队列
_use_Queue_BFS.push(_pMazeArray[x + 1][y]);
//先看看走到终点木有?
if (check_is_end(_pMazeArray[x+1][y]))
{
return;//找到了 不走了 也不需要把终点位置去记录给别人
}
}
//下边不行,往左
if (_pMazeArray[x][y].DirectArr[LEFT] == YES)
{
//走了就把路封起来
_pMazeArray[x][y].DirectArr[LEFT] = NO;
_pMazeArray[x][y - 1].DirectArr[RIGHT] = NO;
//下面这句话是 记录当前节点的二维坐标到下一个节点(左节点)的一维数组中去
_result_node_information[x * _Row + y - 1]
= _pMazeArray[x][y];
//把左边的入队列
_use_Queue_BFS.push(_pMazeArray[x][y - 1]);
//先看看走到终点木有?
if (check_is_end(_pMazeArray[x][y-1]))
{
return;//找到了 不走了 也不需要把终点位置去记录给别人
}
}
if (_pMazeArray[x][y].DirectArr[UP] == YES)//左边不行,往上
{
_pMazeArray[x][y].DirectArr[UP] = NO;//走了就把路封起来
_pMazeArray[x - 1][y].DirectArr[DOWN] = NO;
//下面这句话是 记录当前节点的二维坐标到下一个节点(上节点)的一维数组中去
_result_node_information[(x - 1) * _Row + y]
= _pMazeArray[x][y];
//把上边的入队列
_use_Queue_BFS.push(_pMazeArray[x - 1][y]);
//先看看走到终点木有?
if (check_is_end(_pMazeArray[x-1][y]))
{
return;//找到了 不走了 也不需要把终点位置去记录给别人
}
}
/*$$$$$$$$队头的四个方向的下一步都处理结束了$$$$$$$*/
_use_Queue_BFS.pop();//这个队头节点 处理完了
}
}
//上面的while 有两种可能:
//1 走不通,然后把队列所有节点退完了
//2 找到了,路径信息都保存在一维辅助数组里面了
void showMazePath()// 打印迷宫路径搜索的结果
{
if (_use_Queue_BFS.empty())
{
cout << "找遍了 没有这个路径" << endl;
return;
}
//由终点节点出发 向前回溯(在一位数组里面)
int x = _Row - 1;//这个终点是肯定在一维数组里面的
int y = _Col - 1;
while (1)
{
_pMazeArray[x][y].node_val = '*';//修改路径上的值
if (x == 0 && y == 0)
break;
//把这个节点的前一个节点位置 取出来 继续向前回溯
Node node = _result_node_information[x * _Row + y];
x = node.x;
y = node.y;
}
for (int i = 0; i < _Row; ++i)
{
for (int j = 0; j < _Col; ++j)
{
if (_pMazeArray[i][j].node_val == '*')
cout << "*" << " ";
else cout << _pMazeArray[i][j].node_val << " ";
}
cout << endl;
}
}
private:
int _Row;
int _Col;
Node** _pMazeArray;
//判断一下 当前节点所走的下一个节点是否是终点
bool check_is_end(Node& node)
{
return node.x == _Row - 1 && node.y == _Col - 1;
}
queue<Node>_use_Queue_BFS;//遍历中所使用的队列
//保存BFS的最短路径信息 辅助数组
vector<Node>_result_node_information;
};
int main()
{
cout << "请输入迷宫的行列数(例如:10 10):";
int row, col, data;
cin >> row >> col;
Maze maze(row, col); // 创建迷宫对象
cout << "请输入迷宫的路径信息(0表示可以走,1表示不能走):" << endl;
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < col; ++j)
{
cin >> data;
// 可以初始化迷宫节点的基本信息
maze.initNode(i, j, data);
}
}
// 开始设置所有节点的四个方向的状态
maze.setNodeState();
// 开始从左上角搜索迷宫的路径信息了
maze.searchMazePath();
cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << endl;
// 打印迷宫路径搜索的结果
maze.showMazePath();
return 0;
}
题目要求:实现大数的加减法。而且在主函数里面有 对象的输出,所以需要提供 operator+ operator- operator<<这三个方法。如果有时间,可以再实现一下 乘法和除法的实现。
#include
#include
#include
using namespace std;
class BigInt
{//要求实现BigInt的成员方法,对大数据进行加减法 和 输出
public:
BigInt(string str):_strDigit(str){}
private:
string _strDigit;//使用字符串来存储内置类型存不下的大数据
//需要提供以下方法:
friend ostream& operator<<(ostream& out, const BigInt& src);
friend BigInt operator+(const BigInt& src1, const BigInt& src2);
friend BigInt operator-(const BigInt& src1, const BigInt& src2);
};
int main()
{
BigInt int1("9785645649886874535428765");
BigInt int2("28937697857832167849697653231243");
BigInt int3("9785645649886874535428765");
//28937707643477817736572188660008
//28937707643477817736572188660008
cout << int1 + int2 << endl;
//28937688072186517962823117802478
//28937688072186517962823117802478
cout << int1 - int2 << endl;
BigInt int4("123");
BigInt int5("99");
cout << int5 - int4 << endl;
return 0;
}
这里给定的BigInt对象,对外来说看着是BigInt类型的对象,但是对内,主要是看其成员属性其底层也只是一个string对象(成员变量)。这最终遍历的也就是这个字符串对象。
#include
#include
#include //泛型算法
using namespace std;
class BigInt
{//要求实现BigInt的成员方法,对大数据进行加减法 和 输出
public:
BigInt(string str):_strDigit(str){}
//以上构造函数,通过传入一个string对象,来构造大数对象
private:
string _strDigit;//使用字符串来存储内置类型存不下的大数据
//需要提供以下方法:
friend ostream& operator<<(ostream& out, const BigInt& src);
friend BigInt operator+(const BigInt& src1, const BigInt& src2);
friend BigInt operator-(const BigInt& src1, const BigInt& src2);
};
ostream& operator<<(ostream& out, const BigInt& src)
{
out << src._strDigit;
return out;
}
BigInt operator+(const BigInt& src1, const BigInt& src2)
{
/*
个位数开始相加,从后向前遍历 src1 src2
可以把结果存在一个 string对象里面,然后构造函数构造对象返回即可
这两个大数同时从个位开始相加,有可能同时结束,有可能有一个先结束
每一位相加的时候,都要考虑到进位问题(通过一个标识位)
*/
string result;
bool tag = false;//默认没有进位
int len1 = src1._strDigit.length();
int len2 = src2._strDigit.size();
int i = len1 - 1, j = len2 - 1;
for (; i >= 0 && j >= 0; --i, --j)
{
int a = 0;
if (tag)
{
a += 1;
tag = false;
}
a += src1._strDigit[i] - '0' + src2._strDigit[j] - '0';
if (a >= 10)
{
a = a % 10;
tag = true;
}
result.push_back(a + '0');
}
if (i >= 0)//有一个数还没有完
{
for (; i >= 0; --i)
{
int a = 0;
if (tag)//有可能上面还有进位
{
a += 1;
tag = false;
}
a += src1._strDigit[i] - '0';
if (a >= 10)
{
a = a % 10;
tag = true;
}
result.push_back(a + '0');
}
}
else if (j >= 0)
{
for (; j >= 0; --j)
{
int a = 0;
if (tag)//有可能上面还有进位
{
a += 1;
tag = false;
}
a += src1._strDigit[j] - '0';
if (a >= 10)
{
a = a % 10;
tag = true;
}
result.push_back(a + '0');
}
}
if(tag)//最后一位有可能上面遗留下还有进位
result.push_back(1 + '0');
reverse(result.begin(), result.end());
return BigInt(result);
}
BigInt operator-(const BigInt& src1, const BigInt& src2)
{
/*
个位数开始相减,从后向前遍历 src1 src2
可以把结果存在一个 string对象里面,然后构造函数构造对象返回即可
这两个大数同时从个位开始相加减,有可能同时结束,有可能有一个先结束
每一位相减的时候,都要考虑到借位问题(通过一个标识位)
*/
string result;
bool tag = false;//默认没有借位
int len1 = src1._strDigit.length();
int len2 = src2._strDigit.size();
bool data_symbol = true;//表示正数
string maxStr_src = src1._strDigit;//默认
string minStr_src = src2._strDigit;
if (len1 > len2 || (len1 == len2) && src1._strDigit > src2._strDigit)
{
//保持不变
}
else if (len1 < len2||(len1==len2)&&src1._strDigit<src2._strDigit)
{
data_symbol = false;//表示负数
maxStr_src = src2._strDigit;//需要更改
minStr_src = src1._strDigit;
}
else//这两个大数 是一样的
{
result.push_back('0');
return BigInt(result);
}
int i = maxStr_src.size() - 1, j = minStr_src.size() - 1;
for (; i >= 0 && j >= 0; --i, --j)
{
int a = 0;
if (tag)//先看看有没有借位
{
a -= 1;//这里把借位还回去
tag = false;
}
a += (maxStr_src[i] - '0') - (minStr_src[j] - '0');
if (a <0)
{
a = a + 10;//这里发生了借位
tag = true;
}
result.push_back(a + '0');
}
if (i >= 0)//有一个数还没有完
{
for (; i >= 0; --i)
{
int a = 0;
if (tag)
{
a -= 1;
tag = false;
}
a += maxStr_src[i] - '0';
if (a >= 10)
{
a = a + 10;
tag = true;
}
result.push_back(a + '0');
}
}
auto it = result.rbegin();//删除前面的多余数字0 版本二
for (; it != result.rend(); ++it)
{
if (*it != '0')
{
break;
}
}
string str;
for (; it != result.rend(); ++it)
{
str.push_back(*it);
}
/*
reverse(result.begin(), result.end());
for (auto it = result.begin(); it != result.end(); ++it)
{//删除前面的多余数字0 版本一
if (*it == '0')
{
it = result.erase(it);//删除前面的多余数字0
}
else break;
}
*/
if (!data_symbol)//这个结果是个负数
{
string::iterator it = str.begin();
str.insert(it, '-');
}
return BigInt(str);
}
int main()
{
BigInt int1("9785645649886874535428765");
BigInt int2("28937697857832167849697653231243");
BigInt int3("9785645649886874535428765");
//28937707643477817736572188660008
//28937707643477817736572188660008
//
cout << int2 + int3 << endl;
BigInt int4("888");
BigInt int5("999");
cout << int5 - int4 << endl;
cout << int4 - int4 << endl;
//28937688072186517962823117802478
//28937688072186517962823117802478
cout << int2 - int3 << endl;
BigInt int6("123");
BigInt int7("99");
cout << int7 - int6 << endl;
return 0;
}
大量数据求 Top K、查重
(1)值最大的前k个 或者 值最小的前k个。
(2)值第k大 或者 第k小的那和数据是多少
(3)哪些数据是重复的?重复了多少次?
(4)去重复问题
本节重点:海量数据的查重问题
但凡是查重复问题:就是要去使用增删查 近似达到O(1)时间复杂度的哈希表。如果是对于内存没有太大限制的场景,就可以一次使用哈希表。(牺牲空间换时间)。
(在32位Linux系统之下,一个int占4字节。10亿个数据,占内存大概为1*4G)所以说只是把这些数据存起来的话,就需要大量的内存(若是使用链式哈希表,除了数据域还有指针域),当然是不现实的。
(这里我们讨论的都是整数类型的查重)
如果是不允许这么大的内存使用,则这个时候需要使用上 分而治之的思想,把大量的数据量进行一个划分。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
(这里将使用一个Bloom Filter布隆过滤器进行大量数据的查重)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
倘若数据类型是 字符串类型,则也是可以使用哈希表和布隆过滤器的。可是对于字符串类型,我们有更好的处理办法:TrieTree字典树(前缀树)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
(这里将使用一个进行TrieTree字典树(前缀树)大量数据的查重)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
基于哈希表实现的undered_map undered_set。是最好用的工具 其中链式哈希表,是:解决哈希冲突是用链表把产生哈希冲突的数据连接起来,因此每一个节点除了保存整数数据,还需要保存指针域,因此10亿个整数,每个整数在多一个指针大小空间,那么整个链式哈希表算下来,大约需要4G(数据总数)+4G(指针总数)= 8G的内存空间。
问题1 :下面的数组,哪些数字重复了,以及重复了多少次?(没有提及内存的情况,意思就是重在了解解法思想)
#include
#include
#include
using namespace std;
int main()
{
srand((unsigned)time(nullptr));
const int DataSize = 40;//数据量 对内存没有限制
int dataarr[DataSize] = { 0 };
for (int i = 0; i < DataSize; ++i)
{
dataarr[i] = rand() % 40 + 1;
}
/*
问题1 :上面的数组,哪些数字重复了,以及重复了多少次
*/
unordered_map<int, int>mymap;//键是数据本身,值为出现的次数
for (int val : dataarr)
{
mymap[val]++;
/*
【】如果这个键存在则返回对应的值,然后进行++
没有存在 则在mymap里面插入一个val为键,0为值(int类型默认产生)
的pair对象。然后++表示出现了一次
*/
}
//下面是数据的统计结果的输出
for (unordered_map<int, int>::iterator it = mymap.begin();
it != mymap.end(); ++it)
{
if (it->second > 1)
cout << it->first << "出现了:" << it->second << "次" << endl;
}
cout << "**************************" << endl;
for (pair<int,int>mypair : mymap)
{
if (mypair.second > 1)
cout << mypair.first << "出现了:" << mypair.second << "次" << endl;
}
return 0;
}
mymap对象是定义在栈上的,所以数据量太大的情况就会导致。栈空间不够使用,导致程序崩溃
问题2 :给定一个文件(里面有50亿数据),哪些数字重复了,以及重复了多少次
内存限制在400M
50亿个数据(整数)大概在20G的大小。倘若再使用上哈希表(40G:这是最差的结果了),所以不可能一下子把全部的数据都加载到这个Map表里面。
从大文件里面读出每一个data,然后mod文件个数(哈希映射函数:除留余数法),把这个数据放到对应序号的小文件中。为了减少哈希冲突,最好是个素数。这样做的话,值相同又经过同一个哈希映射函数,当然最后放在了同一个小文件中。(当然为了减少密集,把小文件个数上提(这样一个小文件的大小就不会超过400M了)。一个进程允许文件的个数大概到1024个)
然后对于每个小文件就可以读入到内存当中了,然后进行类似于上面的操作了。
这就是分而治之的思想
例子如下:这里50G/400M =128个 。做法就是将大文件的数据都通过除留余数法,把数据分散到众多的小文件里面,然后这些小文件每次都是可以直接加载到有所限制的内存之中的,然后进行操作。最终合并这些小文件的结果,得到最终的结果。
代码如下:
#include
#include
#include
#include
using namespace std;
/*
问题2 :给定一个文件(里面有50亿数据),哪些数字重复了,以及重复了多少次
内存限制在400M
*/
#define _CRT_SECURE_NO_WARNINGS
int main()
{
//问题描述如下:求下面这个大数据文件中的哪些数字重复,以及重复次数
srand((unsigned)time(nullptr));
const int DataSize = 15;//数据量 对内存有限制
unsigned int* filebuffer = new unsigned int[DataSize];//开辟缓冲区
FILE* pfile = fopen("test_data.txt", "wb");
if (pfile == nullptr)
{
return -1;
}
for (int i = 0; i < DataSize; ++i)
{
filebuffer[i] = rand() % 10 + 1;
}
fwrite(filebuffer, sizeof(int), DataSize, pfile);
fclose(pfile);
delete[]filebuffer;
////////////////////////////////////////////////////////
//打开源数据文本大文件
FILE* Bigfile_ptr = fopen("test_data.txt", "rb");
if (nullptr == Bigfile_ptr)
throw"this file is empty!!!";
//下面是进行大文件的数据映射切割
const int small_file_nuber = 3;//小文件个数
FILE* small_file_arr[small_file_nuber] = { nullptr };
for (int i = 0; i < small_file_nuber; ++i)
{
char small_file_name[20];//存放小文件的文件名
//给小文件起名字
sprintf(small_file_name, "test_data%d.txt", i + 1);
//给文件数组每一个文件初始化指针
small_file_arr[i] = fopen(small_file_name, "wb+");
}
//下面是进行 哈希映射 数据分流
int data;
while (fread(&data, 4, 1, Bigfile_ptr) > 0)//每次读一个
{
int file_index = data % small_file_nuber;//哈希映射
//写入数据
fwrite(&data, 4, 1, small_file_arr[file_index]);
}
unordered_map<int, int>mymap;//键是数据本身,值为出现的次数
for (int i = 0; i < small_file_nuber; ++i)//处理这3个小文件
{
// 恢复小文件的文件指针到起始位置
fseek(small_file_arr[i], 0, SEEK_SET);
while(fread(&data,4,1,small_file_arr[i]))
{
mymap[data]++;
/*
【】如果这个键存在则返回对应的值,然后进行++
没有存在 则在mymap里面插入一个val为键,0为值(int类型默认产生)
的pair对象。然后++表示出现了一次
*/
}
//统计结束了
for (pair<int, int>mypair : mymap)
{
if (mypair.second > 0)
cout << mypair.first << "出现了:"
<< mypair.second << "次" << endl;
}
cout << "***************************" << endl;
// 清空哈希表,进行下一个小文件的数据统计
mymap.clear();
}
for (int i = 0; i < small_file_nuber; ++i)//关闭这3个小文件
{
fclose(small_file_arr[i]);
}
return 0;
}
问题3:
10亿个数据,相当于1G的整数。也就是整数就4G了,使用上链式哈希表,就得到8G的结果。需要8G的内存才可以读进去,可是现在内存限制在400M上。约莫分上37个小文件就可以完美解决问题了。
这里的解决方案是:把a和b这两个大文件,划分成个数相等的一些小文件。这里照样使用的是分而治之的策略,比如都划分为37个小文件。
分别从a和b文件里面读取数据data。然后data%37=file_index.得到数据的下标,然后把数据存放在对于号的小文件之中。
毕竟这里采用的是,相同的哈希映射函数。最后处理的时候,是对应下标的小文件两两一起处理。
#include
#include
#include
#include
using namespace std;
/*
问题3 :给定2个文件(里面都有10亿数据),哪些数字重复了,以及重复了多少次
内存限制在400M
这里我采用一个vector将最后的结果数据都保存一下
*/
#define _CRT_SECURE_NO_WARNINGS
int main()
{
//问题描述如下:求下面这2个大数据文件中的哪些数字重复,以及重复次数
srand((unsigned)time(nullptr));
const int DataSize = 15;//数据量 对内存有限制
unsigned int* filebuffer = new unsigned int[DataSize];//开辟缓冲区
FILE* pfile1 = fopen("test_data.txt_A", "wb");//这是给A文件传入数据
if (pfile1 == nullptr)
{
return -1;
}
for (int i = 0; i < DataSize; ++i)
{
filebuffer[i] = rand() % 10 + 1;
}
fwrite(filebuffer, sizeof(int), DataSize, pfile1);
fclose(pfile1);
FILE* pfile2 = fopen("test_data.txt_B", "wb");//这是给B文件传入数据
if (pfile2 == nullptr)
{
return -1;
}
for (int i = 0; i < DataSize; ++i)
{
filebuffer[i] = rand() % 10 + 1;
}
fwrite(filebuffer, sizeof(int), DataSize, pfile2);
fclose(pfile2);
delete[]filebuffer;
////////////////////////////////////////////////////////
vector<pair<int, int>>result_vec;//存放结果的
//打开源数据文本大文件A
FILE* Bigfile_ptr1 = fopen("test_data.txt_A", "rb");
//打开源数据文本大文件B
FILE* Bigfile_ptr2 = fopen("test_data.txt_B", "rb");
if (nullptr == Bigfile_ptr1 ||nullptr==Bigfile_ptr2)
throw"this file is empty!!!";
//下面是进行大文件的数据映射切割
const int small_file_nuber = 3;//小文件个数
//A 的小文件指针
FILE* small_file_arr1[small_file_nuber] = { nullptr };
//B 的小文件指针
FILE* small_file_arr2[small_file_nuber] = { nullptr };
for (int i = 0; i < small_file_nuber; ++i)
{
char small_file_name[20];//存放小文件的文件名
//给A 的小文件起名字
sprintf(small_file_name, "A_test_data%d.txt", i + 1);
//给文件数组每一个文件初始化指针
small_file_arr1[i] = fopen(small_file_name, "wb+");
//给B 的小文件起名字
sprintf(small_file_name, "B_test_data%d.txt", i + 1);
//给文件数组每一个文件初始化指针
small_file_arr2[i] = fopen(small_file_name, "wb+");
}
//下面是进行 哈希映射 数据分流
int data;
while (fread(&data, 4, 1, Bigfile_ptr1) > 0)//A 文件每次读一个
{
int file_index = data % small_file_nuber;//哈希映射
fwrite(&data, 4, 1, small_file_arr1[file_index]);//写入数据
}
while (fread(&data, 4, 1, Bigfile_ptr2) > 0)//B 文件每次读一个
{
int file_index = data % small_file_nuber;//哈希映射
fwrite(&data, 4, 1, small_file_arr2[file_index]);//写入数据
}
unordered_map<int, int>mymap;//键是数据本身,值为出现的次数
for (int i = 0; i < small_file_nuber; ++i)//处理这3个小文件
{
// 恢复小文件的文件指针到起始位置
fseek(small_file_arr1[i], 0, SEEK_SET);
fseek(small_file_arr2[i], 0, SEEK_SET);
//先统计A的index号小文件
while(fread(&data,4,1,small_file_arr1[i]))
{
mymap[data]++;
/*
【】如果这个键存在则返回对应的值,然后进行++
没有存在 则在mymap里面插入一个val为键,0为值(int类型默认产生)
的pair对象。然后++表示出现了一次
*/
}
//再统计B的index号小文件
while (fread(&data, 4, 1, small_file_arr2[i]))
{
mymap[data]++;
}
//统计结束了
for (pair<int,int>val : mymap)
{
result_vec.push_back(val);
}
// 清空哈希表,进行下一个小文件的数据统计
mymap.clear();
}
for (int i = 0; i < small_file_nuber; ++i)//关闭这3个小文件
{
fclose(small_file_arr1[i]);
fclose(small_file_arr2[i]);
}
bool tag = true;
for (int i=0;i<result_vec.size();++i)
{
pair<int, int>& val = result_vec[i];
if (val.first % 3 == 0 && tag)
{
while (tag)
{
cout << "mod3=0的 如下:" << endl;
tag = false;//只让上面那句打印一次
}
}
if (val.first % 3 == 0)
{
cout << val.first << "出现了" << val.second << "次" << endl;
continue;
}
if (val.first % 3 == 1 && !tag)
{
while (!tag)
{
cout << "******************************" << endl;
cout << "mod3=1的 如下:" << endl;
tag = true;//只让上面那句打印一次
}
}
if (val.first % 3 == 1)
{
cout << val.first << "出现了" << val.second << "次" << endl;
continue;
}
if (val.first % 3 == 2 && tag)
{
while (tag)
{
cout << "******************************" << endl;
cout << "mod3=2的 如下:" << endl;
tag = false;//只让上面那句打印一次
}
}
if (val.first % 3 == 2)
{
cout << val.first << "出现了" << val.second << "次" << endl;
continue;
}
}
return 0;
}
假如这里如果找的是前K大元素,需要的是小根堆。具体做法:把这么多数据的前k个元素放进去这个小根堆里面,(找前K大元素,就是在逐渐淘汰小值的过程),于是这前K个元素的最小值在堆顶存在。然后再逐渐遍历余下的整数,这些整数都和此时的堆顶进行比较。
(1)如果比堆顶元素小,则继续遍历下一个
(2)比堆顶大,则证明此时的堆顶元素,肯定不是前k大的元素。于是小根堆的堆顶出堆,然后这个比较大点的元素入堆。
(3)直至剩下数据遍历结束,此时的小根堆里面就保存的是前K大元素。
例如:
如果是找前K小元素,需要的是大根堆。 具体做法:把这么多数据的前k个元素放进去这个大根堆里面,(找前K小元素,就是在逐渐淘汰大值的过程),于是这前K个元素的最大值在堆顶存在。然后再逐渐遍历余下的整数,这些整数都和此时的堆顶进行比较。
(1)如果比堆顶元素大,则继续遍历下一个
(2)比堆顶小,则证明此时的堆顶元素,肯定不是前k小的元素。于是小根堆的堆顶出堆,然后这个比较小点的元素入堆。
(3)直至剩下数据遍历结束,此时的大根堆里面就保存的是前K小元素。
如果是找第K小元素,需要的是大根堆的堆顶
如果是找第K大元素,需要的是小根堆的堆顶
写代码的时候,我们也不需要去自定义 大小根堆:
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
解法2:快排分割函数(效率会比大小根堆更高一些)
假如问题场景如下:共8个数据
对于快排分割函数而言,一趟下来:暂定把23作为基准数 ,可以使得小于23的调整到左边;大于23的调整到右边。基准数23 就可以算是调整结束了 O(log n)
实现步骤如下:
(1)把23先保存起来。从后向前,16小于23,则16调整到23位置处。如下:
(2)然后从左再向右找,遇到了45,大于23,则45调整到最后面位置上。
(3)再从右向左找:找到一个比23小的。5调整
(4)不存在从左往右,比23 小的值了。于是23可以归位了
于是、于是、于是一趟快排分割函数,23的位置也就调整好了。小于23的调整到左边;大于23的调整到右边。如下:
23 的数组下标是3(index),表示着23 是整个数组的第4(index+1)小的数据。而且前4小的数据就是23 前面的和23本身。
如果是找前k大的话,表示着23 是整个数组的第5大的数据。而且前4大的数据就是23 后面的和23本身。
如果这里找的是第2小的,则说明这个数据在23的左面。然后在23的左边进行 一次快排分割函数。如果这里找的是第6小的,则说明这个数据在23的右面。然后在23的右边进行 一次快排分割函数。
经过快排分割函数,能够在O(lgn)时间内,把小于基准数的整数调整到左边,把大于基准数的整数调整到右边,基准数(index)就可以认为是第(index+1)小的整数了,也即 [0,(index)]就是前index+1小的整数了
问题1:求vector容器中元素值最大的前10个数字。代码如下:
#include
#include
#include
#include
#include
using namespace std;
int main()
{
/*
问题描述:求vector容器中元素值最大的前10个数字
*/
vector<int> vec;
for (int i = 0; i < 100000; ++i)
{
vec.push_back(rand() + i);
}
// 算法的时间复杂度:O(n)
//优先级队列 默认是一个大根堆,函数对象默认less,先更改为greater函数对象
// 定义小根堆 priority_queue maxHeap;
priority_queue<int, vector<int>, greater<int>> minHeap;
// 先往小根堆放入10个元素
int k = 0;
for (; k < 10; ++k)
{
minHeap.push(vec[k]);
}
//构建一个小根堆,堆顶默认是最小的值
/*
遍历剩下的元素依次和堆顶元素进行比较,如果比堆顶元素大,
那么删除堆顶元素,把当前元素添加到小根堆中,元素遍历完成,
堆中剩下的10个元素,就是值最大的10个元素
*/
for (; k < vec.size(); ++k)
{
if (vec[k] > minHeap.top()) // O(log_2_10)
{
minHeap.pop();
minHeap.push(vec[k]);
}
}
// 打印结果 这个是找前K个,如果是找第K个,那么只打印堆顶元素就可以了
while (!minHeap.empty())
{
cout << minHeap.top() << " ";
minHeap.pop();
}
cout << endl;
return 0;
}
问题2:求vector容器中元素值第10个小的数字。算法的时间复杂度O(log n)。代码如下:
#include
#include
#include
#include
#include
using namespace std;
/*
快排分割函数,选择arr[i]号元素作为基数,把小于arr[i]的元素
调整到左边,把大于arr[i]的元素调整到右边并返回基数位置的下标
*/
//待排序的数组的起始下标 和 末尾下标
int partation(vector<int>& arr, int i, int j)
{
int k = arr[i];//此时的k就是基准数
while (i < j)
{
while (i < j && arr[j] >= k)//从后往前找比基准数小的值
j--;
if (i < j)//把这个小一点的进行一个赋值移动
arr[i++] = arr[j];
while (i < j && arr[i] < k)//从前往后找比基准数大的值
i++;
if (i < j)//把这个大一点的进行一个赋值移动
arr[j--] = arr[i];
}
arr[i] = k;//给基准数归为
return i;//返回基准数的下标
}
/*
params:
1.vector &arr: 存储元素的容器
2.int i:数据范围的起始下标
3.int j:数据范围的末尾下标
4.int k:第k个元素
功能描述:通过快排分割函数递归求解第k小的数字,并返回它的值
*/
int selectNoK(vector<int>& arr, int i, int j, int k)
{
int pos = partation(arr, i, j);//得到一次快排分割后的基准数下标
if (pos == k - 1)
return pos;//找到了,返回这个POS可以做更多事情
else if (pos < k - 1)
return selectNoK(arr, pos + 1, j, k);//右边的下一个位置接着找
else
return selectNoK(arr, i, pos - 1, k);//左边的上一个位置接着找
}
int main()
{
/*
求vector容器中元素第10小的元素值 前10小的
*/
vector<int> vec;
for (int i = 0; i < 100000; ++i)
{
vec.push_back(rand() + i);
}
// selectNoK返回的就是第10小的元素的值
int pos = selectNoK(vec, 0, vec.size() - 1, 10);
cout << vec[pos] << endl; // 第10小的
// 如果要找前10小的,访问[0,pos]就是了
// 额外的需求
/*
有一个大文件,里面放的是整数,内存限制200M,求最大的前10个
采用的计算分治的思想了
大致计算一下整数文件的大小 / 200M = 要分的小文件的数量
哈希映射: 整数 % 小文件的个数 = file_index
然后现在每一个小文件就可以加载到内存当中了,就可以对每一个小
文件的整数求top k元素了,然后合并小文件结果即可
*/
return 0;
}
之前两节已经总结了 海量数据的两种问题场景:
本节主要讨论的是:海量数据重复次数最大/第k大的数据值是什么?
解法就是二种情形的综合考量:哈希表统计(使用映射表统计重复)+大小根堆/快排分割函数。联合求解问题。
/*
问题描述:
求vector容器中元素重复次数最大的第10个的元素值 前10多的元素值
*/
srand((unsigned)time(nullptr));
vector<int> vec;
for (int i = 0; i < 100; ++i)
{
vec.push_back(rand()%100 + 1);
}
这里要求的是 重复次数最大的第10 或者 前十多的数据值。
重复次数的统计如下:
unordered_map<int, int>mymap;//进行数据的统计
/*
【】运算符重载函数
拿val数字在map中查找,如果val不存在,numMap[val]会插入一个[val, 0]
返回其值0的引用。这么一个返回值,然后++,得到一个[val, 1]这么一组新数据
如果val存在,numMap[val]刚好返回的是val数字对应的second重复的次数,
直接++,即可。
*/
for (int val : vec)
{
mymap[val]++;
}
于是就把上面的数据源的全部数据和其重复次数都求出来了。
然后就需要在此基础之上,求出重复次数最多的k大,或者第K。
我们选择 小根堆进行查找。(当然这里使用快排分割也行,只是我需要的不仅仅是重复次数的排名,我还需要保存一下 重复次数最多的值 是谁?)
或者如下:
//这里需要的是一个小根堆类型 数字和重复的次数
typedef priority_queue<pair<int, int>, vector<pair<int, int>>,
function<bool(pair<int, int>&, pair<int, int>&)>>MinHeap;
//自定义小根堆的比较方式
MinHeap minheap([](pair<int, int>& src1, pair<int, int>& src2)->bool
{
return src1.second > src2.second;
});
如上:priority_queue 默认是一个大根堆,所以需要重新定义小根堆结构。自定义这个priority_queue,就是为了priority_queue的元素比较方式。priority_queue的第一个参数是元素类型,第二个参数是priority_queue 底层所依赖的容器的类型。(在数组上构建一个堆,才是可行的。毕竟是通过下标的相对位置来表示的)在第三个参数上(函数对象类型),需要传入一个函数对象(自定义小根堆的比较方式)。function函数对象类型接收bool(pair&,pair&)的类型函数,我们没必要去定义一个类型,这里函数对象类型接收传入lambda表达式。
MinHeap minheap([](pair<int, int>& src1, pair<int, int>& src2)->bool
{
return src1.second > src2.second;
});
这句话 是在定义小根堆对象,然后传入一个函数对象来指导编译器进行pair
int main()
{
/*
问题描述:
求vector容器中元素重复次数最大的第10个的元素值 前10多的元素值
*/
srand((unsigned)time(nullptr));
vector<int> vec;
for (int i = 0; i < 100; ++i)
{
vec.push_back(rand()%100 + 1);
}
unordered_map<int, int>mymap;//进行数据的统计
/*
拿val数字在map中查找,如果val不存在,numMap[val]会插入一个[val, 0]
返回其值0的引用。这么一个返回值,然后++,得到一个[val, 1]这么一组新数据
如果val存在,numMap[val]刚好返回的是val数字对应的second重复的次数,
直接++,即可。
*/
for (int val : vec)
{
mymap[val]++;
}
//统计结束
//这里需要的是一个小根堆类型 数字和重复的次数
typedef priority_queue<pair<int, int>, vector<pair<int, int>>,
function<bool(pair<int, int>&, pair<int, int>&)>>MinHeap;
//自定义小根堆的比较方式
MinHeap minheap([](pair<int, int>& src1,
pair<int, int>& src2)->bool
{
return src1.second > src2.second;
});
//找前10 重复次数多的元素值
// 先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
unordered_map<int, int>::iterator it = mymap.begin();
for (int i = 0;i < 10 && it!=mymap.end(); ++i,++it)
{
minheap.push(*it);
}
//继续遍历剩余的元素
for (; it != mymap.end(); ++it)
{
//如果map表中当前元素重复次数大于堆顶元素的重复次数,则替换
if (it->second > minheap.top().second)
{
minheap.pop();
minheap.push(*it);
}
}
// 堆中剩下的就是重复次数最大的前k个
while (!minheap.empty())
{
//auto& pair = minheap.top();
const pair<int, int>& pair = minheap.top();
cout << pair.first << " : " << pair.second << endl;
minheap.pop();
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
using namespace std;
/*
快排分割函数,选择arr[i]号元素作为基数,把小于arr[i]的元素
调整到左边,把大于arr[i]的元素调整到右边并返回基数位置的下标
*/
int main()
{
/*
问题描述:
有一个大文件,内存限制200M,求文件中重复次数最多的前10个
大文件 =》 小文件
大文件里面的数据 =》 哈希映射 =》 把数据离散的放入小文件当中
大文件划分小文件(哈希映射)+ 哈希统计 + 小根堆(快排分割)
这里如果使用快排分割,比小根堆块,毕竟不需要把全部数据
都给遍历一遍
*/
srand((unsigned)time(nullptr));
const int DataSize = 500;//数据量 对内存有限制
int* filebuffer = new int[DataSize];//开辟缓冲区
FILE* pfile = fopen("data.txt", "wb");
if (pfile == nullptr)
return -1;
for (int i = 0; i < DataSize; ++i)
{
filebuffer[i] = rand()%100 + 1;
}
fwrite(filebuffer, sizeof(int), DataSize, pfile);
fclose(pfile);
delete[]filebuffer;
//打开源数据文本大文件
FILE* Big_file_ptr = fopen("data.txt", "rb");
if (Big_file_ptr == nullptr)
throw"this file is empty";
//下面是进行大文件的数据映射切割
const int small_file_nuber = 3;//小文件个数
FILE* small_file_array[small_file_nuber] = { nullptr };
for (int i = 0; i < small_file_nuber; ++i)
{
//存放小文件的文件名
char small_file_name[20];
//给小文件起名字
sprintf(small_file_name, "data%d.txt", i + 1);
//给文件数组每一个文件初始化指针
small_file_array[i] = fopen(small_file_name, "wb+");
}
//下面是进行 哈希映射 数据分流
int data;//每次读一个
while (fread(&data, sizeof(int), 1, Big_file_ptr)>0)
{
//哈希映射
int data_index = data % small_file_nuber;
//向对应的文件里面写入数据
fwrite(&data, sizeof(int), 1, small_file_array[data_index]);
}
///////////////////////////////////////////////////////////////
// 定义一个链式哈希表
unordered_map<int, int>mymap;//进行数据的统计
//这里需要的是一个小根堆类型 数字和重复的次数
typedef priority_queue<pair<int, int>, vector<pair<int, int>>,
function<bool(pair<int, int>&, pair<int, int>&)>>MinHeap;
//自定义小根堆的比较方式
MinHeap minheap([](pair<int, int>& src1,
pair<int, int>& src2)->bool
{
return src1.second > src2.second;
});
//找前10 重复次数多的元素值
// 先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
// 分文件求解小文件的top 10大的数字,并求出最终结果
for (int i = 0; i < small_file_nuber; ++i)
{
// 恢复小文件的文件指针到起始位置
fseek(small_file_array[i], 0, SEEK_SET);
//接下来做的第一件事就是在这个小文件里面,统计元素出现的次数
while ((fread(&data, sizeof(int),1,small_file_array[i]) > 0))
{
mymap[data]++;//统计当前小文件里面的数据
}
//再接下来做的一件事就是 根据上面的mymap 构建小根堆进行查找
unordered_map<int, int>::iterator it = mymap.begin();
//下面的if判断非常重要,不然的话3个文件 每个一次,这样mymap的容量就是30个
if (minheap.empty())// 如果堆是空的,先往堆方10个数据
{
// 先从map表中读10个数据到小根堆中,建立top 10的小根堆,最小的元素在堆顶
for (int i = 0; i < 10 && it != mymap.end(); ++i, ++it)
{
minheap.push(*it);
}
}
//继续遍历剩余的元素
for (; it != mymap.end(); ++it)
{
//如果map表中当前元素重复次数大于堆顶元素的重复次数,则替换
if (it->second > minheap.top().second)
{
minheap.pop();
minheap.push(*it);
}
}
// 清空哈希表,进行下一个小文件的数据统计
mymap.clear();
}
for (int i = 0; i < small_file_nuber; ++i)//关闭这3个小文件
{
fclose(small_file_array[i]);
}
// 堆中剩下的就是重复次数最大的前k个
while (!minheap.empty())
{
auto& pair = minheap.top();
cout << pair.first << " : " << pair.second << endl;
minheap.pop();
}
return 0;
}
注:vector采用clear()函数的时候只能清除元素,并不能清除内存,而要清除内存则可以用swap()。
vector<int> v;
for(size_t i = 0; i < 100; ++i)
{
v.push_back(i);
}
cout << "size=" << v.size() << " capacity=" << v.capacity() << endl;
v.clear();
cout << "After clear(): " << v.size() << " " << v.capacity() << endl;
vector<int> (v).swap(v);
cout << "After swap(): " << v.size() << " " << v.capacity() << endl;
结果如下:
size=100 capacity=128
After clear(): 0 128
After swap(): 0 0
这里的capacity是指vector v的容量,也就是占用的内存,最开始之所以容量是128而不是100,是因为vector在push_back的时候容量是按照2的指数增长的。当调用clear()函数之后,虽然size变成了0,但是其占据的内存并没有变,调用swap函数之后才能彻底的清除。