聊一聊二分查找的实现

本文介绍了二分查找的基本原理及其在排序数组中查找特定值的应用。通过对比哈希查找,突出了二分查找在条件和范围查找上的优势。此外,还详细讲解了如何实现基本的二分查找以及如何进行条件限制的查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分查找其实原理很简单,基于排序的数组每次找中间index的值与目标值做比较,达到快速查找的目的。因此带有二分策略的算法,复杂度都是log(n)级别的。

与hash的对比

hash查找通常是常量级别的,优势是比二分查找更快,但是由于缺乏排序查找的功能,因此在基于范围的查找,或者有条件的模糊查找方面是很无能为力的。比如比<=val的最大的数,>=val最小的数,第一个==val的数,最后一个等于val的数。

实现

  • 首先看一看基本的二分查找等于实现,这一段代码其实并没有什么好说的,哪里都能看到

    int FindEqual(int a[], int left, int right, int val)
    {
        while (left <= right)
        {
            int mid = (left + right) / 2;
            if (a[mid] == val)
            {
                return mid;
            }
            else if (a[mid] < val)
            {
                left = mid + 1;
            }
            else
            {
                right = mid - 1;
            }
        }
    
        return -1;
    }
  • 有条件的二分查找,找<=val的值,那么要注意如下几点:

    • 最大的符合条件的,那么最终应当返回left,因为left能不断向右走,逼近结果
    • 确保left是当前最右的符合条件的答案,right之后都不是答案,right可能是答案
    • left每次只能赋值为mid,不能是mid+1,因为mid可能就是答案,而mid+1就把答案越过去了,除非添加额外逻辑才能找回
    • 要确保mid每次都不同,那么必须mid向上取整,否则会陷入mid相等的死循环

    代码如下

    int FindUpper(int a[], int left, int right, int val)
    {
        while (left < right)
        {
            int mid = (left + right + 1) >> 1; // 确保mid不会死循环
            if (a[mid] <= val)
            {
                left = mid;
            }
            else
            {
                right = mid - 1;
            }
        }
    
        return left;
    }

    同理查找>=val的值,与之类似

    // >= val
    int FindLower(int a[], int left, int right, int val)
    {
        while (left < right)
        {
            int mid = (left + right) >> 1;
            if (a[mid] >= val)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }
    
        return right;
    }

如果想要查找等于,那么返回right之前比较一下right下表是否等于val,不等于就是-1了。

第一次写博客,FB留在这里了,撒花。

<think>嗯,用户想要用C++在VisualStudio 2022上实现天室,支持群、单和文件传输。我得先理清楚需求,然后考虑怎么设计这个程序。首先,天室通常需要服务器和客户端两部分。服务器处理多个客户端的连接,转发消息,管理用户状态等。客户端则负责用户界面和与服务器的通信。 接下来,通信协议的选择。由于是C++,可能用原生的Socket编程,使用Winsock库。需要确定消息的格式,比如每个消息可能有类型字段,比如群、单、文件传输,还有发送者、接收者、内容等信息。结构体或者JSON格式?但C++处理JSON可能需要第三方库,可能用户希望用标准库,所以可能用结构体打包数据。然后是多线程处理,服务器需要为每个客户端连接创建个线程,处理接收和发送数据。客户端也需要多线程,个用于接收服务器消息,另个用于用户输入。否则界面会卡住。 文件传输方面,需要考虑分块传输,尤其是大文件。可能需要将文件分成多个包发送,服务器再转发给目标客户端。或者直接建立P2P连接?但可能复杂,所以还是通过服务器中转比较稳妥。 Visual Studio2022的配置方面,需要链接Winsock库,即ws2_32.lib,在项目属性里添加。代码中要初始化Winsock,使用WSAStartup。 然后具体实现服务器和客户端的结构。服务器的主循环接受客户端连接,每个客户端个线程。客户端连接后发送自己的用户名,服务器维护个用户列表,包括socket和用户名等信息。当客户端发送消息时,服务器根据消息类型(群、单)转发给对应的用户。 对于单,消息中需要指定目标用户名,服务器查找该用户对应的socket,发送消息。群则广播给所有在线用户。 文件传输的话,客户端发送文件请求,服务器通知接收方,接收方同意后,开始传输。或者直接通过服务器中转文件数据。这里可能需要建立另个连接或者使用同个连接,用不同的消息类型区分。例如,发送文件时,先发送文件信息(文件名、大小),然后分块发送数据,接收方按块写入文件。 接下来考虑代码结构。服务器端: 1. 创建监听socket,绑定端口,进入监听状态。 2.接受客户端连接,为每个客户端创建线程处理通信。3. 维护用户列表,使用互斥锁保证线程安全。 4. 处理接收到的消息,根据类型转发。 客户端: 1.连接服务器,输入用户名。 2. 启动接收线程,循环接收服务器消息。 3. 用户输入命令,如发送消息、文件等,处理并发送给服务器。 可能的代码结构: 服务器端使用多线程,每个客户端对应个线程。用vector或map保存客户端信息,加锁保护。消息解析部分需要处理不同的命令,比如群消息以特定格式开头,单消息包含目标用户,文件传输可能有特殊标识。 客户端同样使用多线程,主线程处理用户输入,子线程处理接收服务器消息。用户输入的命令需要解析,比如输入"/msg username message"表示单,输入"/fileusername filepath"发送文件。 文件传输部分,发送方读取文件内容,分成多个包发送,每个包包含文件数据和序号。接收方按顺序接收并写入文件。或者,在服务器中转的情况下,服务器需要正确处理文件数据块并转发给目标客户端。 测试时需要注意端口开放,防火墙设置,以及多客户端的并发处理。 现在,代码示例需要包含服务器和客户端的基本框架,处理连接,消息转发,文件传输可能需要较复杂的处理,可能需要分步骤实现,比如先实现和单,再添加文件传输功能。 可能的代码结构: 服务器:- 使用Winsock,创建socket,bind,listen,accept循环。 - 每个客户端线程中,recv接收数据,解析消息类型,处理群、单或文件。 -用户列表用结构体存储socket、用户名等。 客户端: - 连接服务器,发送用户名。 - 接收线程循环recv,显示消息。- 主线程处理用户输入,发送命令和消息。 文件传输可能需要额外的处理,比如当用户发送文件时,客户端读取文件,发送文件头信息(类型、接收者、文件名、大小),然后分块发送数据。服务器接收后转发给目标客户端,目标客户端接收并保存文件。 但具体实现中,大文件传输需要注意缓冲区大小,可能使用循环发送和接收,直到整个文件传输完毕。此外,可能需要确认机制,确保数据包的正确传输。 综上,给出个基础版本的代码,包含群、单,文件传输部分可能较为简化,比如通过base64编码发送小文件,或者分块传输。由于代码长度限制,可能需要分部分展示,但用户要求完整代码,所以需要整合。 现在,编写示例代码的大纲: 服务器端代码: -包含必要的头文件,如WinSock2.h,ws2tcpip.h,iostream,string,vector,thread,mutex等。 -定义常量,如端口号,缓冲区大小。 -定义客户端信息结构体,包含socket、用户名等。 - 全局变量:客户端列表,互斥锁。 - 函数:处理客户端通信的线程函数,接收数据,解析消息类型,转发。 - main函数:初始化Winsock,创建监听socket,绑定,监听,循环accept,创建线程。客户端代码: -包含头文件,连接服务器,输入用户名。 -接收线程函数,循环接收数据显示。 - 主线程处理用户输入,解析命令(群、单、文件),发送消息。 文件传输部分,可能需要在消息类型中添加文件头,处理文件的分块发送和接收。例如,当用户输入/file user filename时,客户端读取文件,发送文件信息,然后发送数据块。 可能的代码实现步骤: 1. 定义消息结构,使用结构体,包含消息类型(群、单、文件),发送者,接收者,内容等。但由于结构体在网络传输中需要序列化,可能更简单的方式是用字符串协议,比如用特定的分隔符分隔各部分。 例如,消息格式: 群: "MSG_ALL|发送者|消息内容"单: "MSG_ONE|发送者|接收者|消息内容" 文件传输开始: "FILE_START|发送者|接收者|文件名|文件大小" 文件数据块: "FILE_DATA|发送者|接收者|数据..." 文件传输结束: "FILE_END|发送者|接收者"这样服务器可以根据前缀解析消息类型,并处理转发。在代码中,服务器接收数据后,解析字符串,根据前缀判断类型,然后处理。客户端发送文件时,先发送FILE_START,然后分块读取文件,发送FILE_DATA,最后发送FILE_END。 接收方客户端在收到FILE_START时,创建文件,准备写入;收到FILE_DATA时追加数据;收到FILE_END时关闭文件。 但需要注意粘包问题,因为TCP是流式协议,消息可能被合并或拆分。因此,可能需要自定义协议,比如在每个消息前加上长度前缀,或者使用定长消息头。 这会增加复杂度,但为了正确处理,可能需要这样做。例如,每个消息的前4字节表示消息体的长度,然后读取相应长度的数据。这需要修改代码,在发送消息时,先发送长度,再发送数据。接收方先读取4字节的长度,再读取对应长度的数据。这样处理可以解决粘包问题,但会增加代码的复杂度。 考虑到用户可能需要个简单实现,可能先忽略粘包问题,假设每次recv都能获取完整消息,但这在实际中不可靠。因此,更好的做法是处理消息长度前缀。 但为了简化示例代码,可能在示例中使用简单的字符串分割,但实际应用中需要更健壮的处理。 综上,示例代码可能如下: 服务器端代码框架: #include<WinSock2.h> #include<ws2tcpip.h>#include <iostream>#include <string>#include <vector>#include <thread>#include <mutex> #pragma comment(lib, "ws2_32.lib") using namespace std; const int PORT= 12345; const int BUFFER_SIZE =1024; structClientInfo {SOCKET socket; string username; }; vector<ClientInfo> clients; mutex clientsMutex; voidHandleClient(SOCKET clientSocket){ char buffer[BUFFER_SIZE]; int bytesReceived; stringusername; //接收用户名bytesReceived =recv(clientSocket, buffer, BUFFER_SIZE,0); if(bytesReceived <=0) {closesocket(clientSocket); return; }username = string(buffer, bytesReceived);{ lock_guard<mutex> lock(clientsMutex);clients.push_back({clientSocket,username}); }cout << username<< " 已连接。" << endl; while (true) {bytesReceived = recv(clientSocket,buffer, BUFFER_SIZE, 0); if (bytesReceived <=0) {break; }string message(buffer, bytesReceived);// 解析消息类型if (message.find("MSG_ALL|") == 0) { //群发lock_guard<mutex>lock(clientsMutex); for(const auto&client : clients){ if (client.socket !=clientSocket) {send(client.socket, message.c_str(), message.size(), 0);} }} else if(message.find("MSG_ONE|") == 0) { //单,解析接收者size_t pos1 = message.find('|',8); stringreceiver = message.substr(8, pos1 - 8); string actualMsg = message.substr(pos1 +1); lock_guard<mutex> lock(clientsMutex);for (constauto& client :clients) {if (client.username== receiver) {send(client.socket, message.c_str(), message.size(), 0);break;} }} else if (message.find("FILE_START|")== 0){ //文件传输开始,转发给接收者//类似单处理//需要解析接收者,并转发// 这里可能需要更复杂的处理,比如接收者确认等// 此处简化为直接转发size_t pos1 = message.find('|',11); stringreceiver = message.substr(11, pos1 - 11); lock_guard<mutex> lock(clientsMutex);for (const auto& client : clients) { if(client.username ==receiver) {send(client.socket, message.c_str(), message.size(),0);break; }} }// 处理其他消息类型,如FILE_DATA,FILE_END等}// 客户端断开处理 {lock_guard<mutex>lock(clientsMutex); autoit = remove_if(clients.begin(),clients.end(), [clientSocket](constClientInfo& ci) { returnci.socket ==clientSocket;}); clients.erase(it, clients.end()); }closesocket(clientSocket); cout << username << "已断开连接。"<< endl; }int main() {WSADATAwsaData;WSAStartup(MAKEWORD(2, 2),&wsaData); SOCKET serverSocket =socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port= htons(PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; bind(serverSocket, (sockaddr*)&serverAddr,sizeof(serverAddr)); listen(serverSocket, SOMAXCONN);cout << "服务器已启动,等待连接..." << endl; while(true) {SOCKETclientSocket = accept(serverSocket,NULL, NULL);if (clientSocket == INVALID_SOCKET){ cerr << "接受连接失败" << endl;continue;} thread(HandleClient, clientSocket).detach(); }closesocket(serverSocket); WSACleanup();return 0; } 客户端代码框架: #include<WinSock2.h> #include<ws2tcpip.h> #include<iostream> #include<string> #include<thread> #pragmacomment(lib,"ws2_32.lib") usingnamespace std; constchar* SERVER_IP = "127.0.0.1";const int PORT =12345;const int BUFFER_SIZE = 1024; SOCKET clientSocket;string username; voidReceiveMessages() {char buffer[BUFFER_SIZE];while (true) { intbytesReceived = recv(clientSocket,buffer, BUFFER_SIZE, 0); if (bytesReceived <=0) {cout << "与服务器断开连接。"<< endl;break; }string message(buffer, bytesReceived);// 解析消息类型if (message.find("MSG_ALL|") == 0) { size_t pos = message.find('|',8);string sender = message.substr(8,pos - 8); string content= message.substr(pos+ 1);cout << "[群]" << sender << ": "<< content << endl; } elseif (message.find("MSG_ONE|") ==0) {size_t pos =message.find('|', 8);string sender =message.substr(8, pos -8); size_t pos2 =message.find('|', pos +1); stringcontent = message.substr(pos2 +1); cout<< "[私]" << sender << " 对你说: " << content<< endl;} else if (message.find("FILE_START|")== 0){ //处理文件传输请求size_tpos = message.find('|',11); stringsender = message.substr(11, pos- 11);size_t pos2 = message.find('|', pos+ 1);string filename =message.substr(pos +1, pos2 - pos -1);// 提示用户接收或拒绝cout << sender<< " 想发送文件给你:"<< filename << endl; //这里需要用户输入是否接收,然后回复服务器// 示例中简化为直接接收,保存文件// 实际需要处理回复,并接收后续数据}// 处理其他消息类型...} } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData);clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);sockaddr_inserverAddr;serverAddr.sin_family = AF_INET; serverAddr.sin_port =htons(PORT); inet_pton(AF_INET, SERVER_IP,&serverAddr.sin_addr); connect(clientSocket, (sockaddr*)&serverAddr,sizeof(serverAddr)); cout << "请输入用户名:"; getline(cin, username); send(clientSocket, username.c_str(), username.size(), 0);thread(ReceiveMessages).detach(); string input; while (true) {getline(cin, input);if (input.empty()) continue;if (input.find("/msg ") ==0) {// 单命令格式:/msg 接收者 消息内容size_tspace1 = input.find(' ',5); if(space1 !=string::npos) { stringreceiver = input.substr(5, space1 - 5); string content= input.substr(space1 +1); stringmessage = "MSG_ONE|"+ username + "|" + receiver+ "|"+ content;send(clientSocket,message.c_str(),message.size(),0); }} else if(input.find("/file ") ==0) {// 文件发送命令格式:/file 接收者文件路径size_t space1 = input.find(' ', 6); if (space1 != string::npos){ string receiver= input.substr(6, space1- 6);string filepath= input.substr(space1 +1); //读取文件并发送 FILE* file = fopen(filepath.c_str(), "rb");if (file) { fseek(file,0, SEEK_END);long fileSize =ftell(file);fseek(file, 0,SEEK_SET);string fileName =filepath.substr(filepath.find_last_of("\\/")+ 1);string header ="FILE_START|" + username+ "|"+ receiver + "|" + fileName+ "|"+ to_string(fileSize); send(clientSocket, header.c_str(), header.size(), 0); char buffer[BUFFER_SIZE]; while (true) {size_t bytesRead= fread(buffer, 1,BUFFER_SIZE,file); if(bytesRead <=0) break; string dataMsg = "FILE_DATA|" +username + "|" + receiver +"|";dataMsg.append(buffer, bytesRead);send(clientSocket, dataMsg.c_str(), dataMsg.size(), 0); }string endMsg ="FILE_END|" + username+ "|"+ receiver;send(clientSocket,endMsg.c_str(), endMsg.size(), 0);fclose(file); } else{ cout << "无法打开文件。" << endl;} }} else {// 群消息string message = "MSG_ALL|"+ username + "|" + input; send(clientSocket, message.c_str(), message.size(), 0);} }closesocket(clientSocket); WSACleanup();return 0; } 这只是个基础框架,实际需要处理更多细节,比如: -文件传输的可靠性,分块传输和重组。- 粘包问题,使用消息长度前缀。 -错误处理,比如连接断开、文件读取失败等。 - 用户界面的友好性,比如接收文件时的确认。 -多线程同步问题。 例如,文件传输部分在客户端接收时,需要处理FILE_START、FILE_DATA和FILE_END消息,将数据写入文件。但当前示例代码中并未实现接收文件的部分,需要补充。 此外,代码中使用了字符串操作来解析消息,可能效率不高,且容易出错。更好的方法是设计个更严谨的协议,比如使用二进制格式,包含消息类型、长度等字段。 由于用户要求完整代码,可能需要更完整的实现,但考虑到篇幅和复杂度,示例代码可能无法涵盖所有功能,尤其是文件传输的完整处理。因此,可能需要在回答中指出这些限制,并提供进步的改进建议。最后,根据用户的需求,给出VisualStudio 2022的配置说明,比如需要链接ws2_32.lib,设置字符集等。</think>以下是基于Winsock实现的C++天室基础代码,包含群和单功能,文件传输功能需要额外扩展: ```cpp // Server.cpp #include <WinSock2.h> #include <ws2tcpip.h> #include <iostream> #include <string> #include <vector> #include <thread> #include <mutex> #pragma comment(lib, "ws2_32.lib") using namespace std; const int PORT = 12345; const int BUFFER_SIZE = 1024; struct ClientInfo { SOCKET socket; string username; }; vector<ClientInfo> clients; mutex clientsMutex; void BroadcastMessage(const string& message, SOCKET excludeSocket = INVALID_SOCKET) { lock_guard<mutex> lock(clientsMutex); for (const auto& client : clients) { if (client.socket != excludeSocket) { send(client.socket, message.c_str(), message.size(), 0); } } } void HandleClient(SOCKET clientSocket) { char buffer[BUFFER_SIZE]; int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0); if (bytesReceived <= 0) { closesocket(clientSocket); return; } string username(buffer, bytesReceived); { lock_guard<mutex> lock(clientsMutex); clients.push_back({clientSocket, username}); } BroadcastMessage(username + " 加入了天室\n", clientSocket); while (true) { bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0); if (bytesReceived <= 0) break; string message(buffer, bytesReceived); if (message.find("/pm ") == 0) { size_t space = message.find(' ', 4); if (space != string::npos) { string targetUser = message.substr(4, space - 4); string privateMsg = message.substr(space + 1); lock_guard<mutex> lock(clientsMutex); for (const auto& client : clients) { if (client.username == targetUser) { send(client.socket, ("[私] " + username + ": " + privateMsg + "\n").c_str(), username.size() + privateMsg.size() + 10, 0); break; } } } } else { BroadcastMessage("[群] " + username + ": " + message + "\n", clientSocket); } } { lock_guard<mutex> lock(clientsMutex); auto it = remove_if(clients.begin(), clients.end(), [clientSocket](const ClientInfo& ci) { return ci.socket == clientSocket; }); clients.erase(it, clients.end()); } BroadcastMessage(username + " 离开了天室\n", clientSocket); closesocket(clientSocket); } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)); listen(serverSocket, SOMAXCONN); cout << "服务器已启动..." << endl; while (true) { SOCKET clientSocket = accept(serverSocket, nullptr, nullptr); if (clientSocket != INVALID_SOCKET) { thread(HandleClient, clientSocket).detach(); } } closesocket(serverSocket); WSACleanup(); return 0; } // Client.cpp #include <WinSock2.h> #include <ws2tcpip.h> #include <iostream> #include <string> #include <thread> #pragma comment(lib, "ws2_32.lib") using namespace std; const char* SERVER_IP = "127.0.0.1"; const int PORT = 12345; const int BUFFER_SIZE = 1024; SOCKET clientSocket; void ReceiveMessages() { char buffer[BUFFER_SIZE]; while (true) { int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0); if (bytesReceived <= 0) { cout << "与服务器断开连接" << endl; exit(0); } cout << string(buffer, bytesReceived); } } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PORT); inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr); if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { cerr << "连接失败" << endl; return 1; } cout << "请输入用户名: "; string username; getline(cin, username); send(clientSocket, username.c_str(), username.size(), 0); thread receiverThread(ReceiveMessages); receiverThread.detach(); while (true) { string input; getline(cin, input); if (input.empty()) continue; if (input == "/exit") { break; } else if (input.find("/pm ") == 0) { send(clientSocket, input.c_str(), input.size(), 0); } else { send(clientSocket, input.c_str(), input.size(), 0); } } closesocket(clientSocket); WSACleanup(); return 0; } ``` 文件传输实现思路: 1. 客户端发送文件传输请求(包含文件名和大小) 2. 服务器转发请求给接收方 3. 接收方确认接收后开始传输 4. 使用二进制模式分块传输文件数据 5. 接收方保存文件到本地 相关问题:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值