lc74 搜索有序二维数组

博客讨论了数组查找的方法。起初认为先纵向二分再横向二分更快,但将整个数组展开二分查找,复杂度与两次查找相同,且认为一次遍历的方法更为简洁。

这道题一开始以为纵向一次二分,然后再横向二分会比较快。

但其实把整个数组展开,二分查找O(log(mn)) = O(logm) + O(logn),其实一样。

两次查找的方法:

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        if(m == 0) return false;
        int n = matrix[0].size();
        if(n == 0) return false;
        
        int i = 0, j = m - 1, mid;
        while(i <= j){
            mid = i + (j - i) / 2;
            if(matrix[mid][0] == target){
                return true;
            } else if(matrix[mid][0] > target){
                j = mid - 1;
            } else {
                i = mid + 1;
            }
        }
        if(j < 0) return false;
        
        int hit = j;
        i = 0, j = n - 1;
        while(i <= j){
            mid = i + (j - i) / 2;
            if(matrix[hit][mid] == target){
                return true;
            } else if(matrix[hit][mid] > target){
                j = mid - 1;
            } else {
                i = mid + 1;
            }
        }
        return false;
    }
};

觉得还是一次遍历的更为简洁一点:

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        if(m == 0) return false;
        int n = matrix[0].size();
        if(n == 0) return false;
        
        int i = 0, j = m * n;
        int mid, mv;
        while(i < j){
            mid = i + (j - i) / 2;
            mv = matrix[mid / n][mid % n];
            if(mv == target){
                return true;
            } else if(mv > target){
                j = mid;
            } else {
                i = mid + 1;
            }
        }
        
        return false;
    }
};

 

# include<iostream> # include<stdio.h> # include<stdlib.h> using namespace std; #define LIST_INIT_SIZE 100 //线性表的初始容量 #define OK 1 #define OVERFLOW -2 #define ERROR 0 typedef int ElemType; //假设ElemType为int类型 typedef int Status; //状态类型,将Status定义为int //定义顺序表结构体 typedef struct{ ElemType *elem; //指向存储数据元素的基地址 int length; //当前线性表的长度 int listsize; //当前分配的存储容量 }SqList; //函数声明 Status InitList(SqList &L); //初始 Status DestroyList(SqList &L); //销毁 Status ClearList(SqList &L); //清空 Status ListEmpty(SqList L); //判断线性表是否为空 int ListLength(SqList L); //获取线性表长度 Status GetElem(SqList L,int i,ElemType &e); //获取指定位置元素 int LocateElem(SqList L,ElemType e); //获取元素位置 Status PriorElem(SqList L,ElemType cure_e,ElemType &pre_e); //获取前驱 Status NextElem(SqList L,ElemType crue_e,ElemType &next_e); //获取后驱 Status ListInsert(SqList &L,int i,ElemType e); //插入元素 Status ListDelete(SqList &L,int i,ElemType &e); //删除指定位置元素 Status DeleteElem(SqList &L,ElemType e); //根据元素值,删除元素 void SortList(SqList &L); //排序 void ShowList(SqList L); //显示 Status MergeList(SqList La,SqList Lb,SqList &Lc); //合并 //初始化线性表 Status InitList(SqList &L) { //构造一个空的线性表L L.elem=(ElemType* )malloc(LIST_INIT_SIZE*sizeof(ElemType)); //为数据元素开辟一维数组空间 // LIST_INIT_SIZE一个宏定义,表示顺序表城市容量 //malloc 函数动态分配一片连续的内存空间 //sizeof(int)计算int类型的变量占几个字节 if(!L.elem) exit(OVERFLOW); //此时!L.elem为真,调用exit(OVERFLOW)终止运行 L.length=0; //空表长度为0 L.listsize=LIST_INIT_SIZE; //初始存储变量 return OK; } //销毁线性表 Status DestroyList(SqList &L){ free(L.elem); //释放内存 L.elem = NULL; //将已释放的指针设置为NULL,防止指针悬空 L.length = 0; L.listsize = 0; //重置属性 return OK; } //清空线性表 Status ClearList(SqList &L){ L.length = 0; return OK; } //判断线性表是否为空 Status ListEmpty(SqList L){ return L.length == 0 ? OK : ERROR; //空则返回OK,否则返回ERROR } //获取线性表长度 int ListLength(SqList L){ return L.length; } //获取指定位置元素 Status GetElem(SqList L,int i,ElemType &e){ if (i < 1 || i > L.length) return ERROR; //检查i是否有效 e = L.elem[i - 1]; //从0开始,所以i-1 return OK; } //获取元素位置 int LocateElem(SqList L,ElemType e){ for (int i = 0;i < L.length;++i){ //遍历 if(L.elem[i] == e) return i + 1; //返回位序 } return 0; } //获取前驱 Status PriorElem(SqList L,ElemType cur_e,ElemType &pre_e){ int pos = LocateElem(L,cur_e); //查找当前元素位置 if (pos <= 1 || pos == 0) return ERROR; pre_e = L.elem[pos - 2]; //下标从0开始,而pose从1开始 return OK; } //获取后继 Status NextElem(SqList L,ElemType cur_e,ElemType &next_e){ int pos = LocateElem(L,cur_e); if (pos == 0 || pos == L.length) return ERROR; next_e = L.elem[pos - 1 + 1]; return OK; } //插入元素 Status ListInsert(SqList &L,int i,ElemType e){ if (i < 1 || i > L.length + 1) return ERROR; //输入位置是否有效 if (L.length >= L.listsize){ ElemType *newbase = (ElemType *)realloc(L.elem,(L.listsize + 10) * sizeof(ElemType)); //使用realloc函数重新分配内存 if (!newbase) exit(OVERFLOW); //扩容失败,调用exit终止函数 L.elem = newbase; L.listsize += 10; } ElemType *p, *q; q = &L.elem[i - 1]; for (p = &(L.elem[L.length - 1]);p>= q;--p){ *(p+1) = *p; } *q = e; ++L.length; //表长度加一 return OK; } //删除指定位置元素 Status ListDelete(SqList &L,int i,ElemType &e){ if (i < 1 || i > L.length) return ERROR; e = L.elem[i - 1]; //将第i个位置的元素赋给e ,使得函数外部可获取到 for (int j = i;j < L.length;++j) L.elem[j - 1] = L.elem[j]; //第i+1位置开始,元素依次向前移动一位 --L.length; return OK; } //根据值删除元素 Status DeleteElem(SqList &L,ElemType e){ int pos = LocateElem(L,e); //将返回的位置存储在变量pos中 if (pos == 0) return ERROR; //未找到元素 ElemType temp; //用于存储删除的元素值 return ListDelete(L,pos,temp); //调用ListDelete删除位置为pos的元素 } //排序(简单冒泡排序) void SortList(SqList &L){ for (int i = 0;i < L.length - 1;++i) //外层循环,控制排序的轮数 for (int j = 0;j < L.length - i - 1;++j) //内层循环,控制每一轮中相邻元素比较交换 if (L.elem[j] > L.elem[j + 1]){ swap(L.elem[j],L.elem[j + 1]); //调用swap函数交换两个函数位置,较大的后移 } } //显示线性表 void ShowList(SqList L){ if (L.length == 0){ //判断顺序表是否为空 printf("线性表为空!\n"); return; } //非空表 printf("线性表内容: \n"); int i; for (i = 0;i < L.length;i++){ printf("%d" ,L.elem[i]); //打印第i个元素 // 如果不是最后一个元素,则输出逗号 if (i < L.length - 1) { printf(","); } } printf("\n"); //所有元素打印 } //合并两个非递减有序线性表 Status MergeList(SqList La,SqList Lb,SqList &Lc) { int i = 0,j = 0,k = 0; Lc.elem = (ElemType *)malloc((La.length + Lb.length) * sizeof(ElemType)); //为Lc分配内存空间,大小为La和Lb的长度之和乘以每个元素的大小 if (!Lc.elem) exit(OVERFLOW); //检查内存分配是否成功 while(i < La.length && j < Lb.length){ if (La.elem[i] <= Lb.elem[j]){ Lc.elem[k++] = La.elem[i++]; }else{ Lc.elem[k++] = Lb.elem[j++]; //将较小的元素放入Lc中 } } while(i < La.length) Lc.elem[k++] = La.elem[i++]; while(j < Lb.length) Lc.elem[k++] = Lb.elem[j++]; //遍历完后剩余元素插入Lc末尾 //对Lc去重 if (k == 0) { Lc.length = 0; Lc.listsize = 0; return OK; } int writeIndex = 1; // 写入位置,第一个元素肯定保留 for (int readIndex = 1; readIndex < k; readIndex++) { // 如果当前元素不同于前一个,则保留 if (Lc.elem[readIndex] != Lc.elem[writeIndex - 1]) { Lc.elem[writeIndex] = Lc.elem[readIndex]; writeIndex++; } } // 更新 Lc 的长度和存储空间 Lc.length = writeIndex; Lc.listsize = writeIndex; return OK; } //状态检查,用于main中判断当前线性表是否可用 #define IS_VALID(L)((L).elem != NULL) #define IS_EMPTY(L)((L).length == 0) int main() { SqList L; L.elem = NULL; //初始状态设置为NULL,表示为初始化 int status; int n = 1;//退出菜单的标记 cout<<"1------初始化一个线性表"<<endl; cout<<"2------销毁线性表"<<endl; cout<<"3------清空线性表"<<endl; cout<<"4------判断线性表是否为空"<<endl; cout<<"5------求线性表长度"<<endl; cout<<"6------获取线性表指定位置元素"<<endl; cout<<"7------获取线性表中元素的位置"<<endl; cout<<"8------求前驱"<<endl; cout<<"9------求后继"<<endl; cout<<"10------在线性表指定位置插入元素"<<endl; cout<<"11------删除线性表指定位置的元素"<<endl; cout<<"12------根据元素值,删除线性表中的元素"<<endl; cout<<"13------对线性表进行排序"<<endl; cout<<"14------显示线性表"<<endl; cout<<"15------合并两个非递减有序的线性表"<<endl; cout<<"退出,输入一个负数"<<endl; while(n){//n不等于0 n!=0 int s; //选菜单 cout<<"请输入操作码,(退出时输入负数):\n"; scanf("%d",&s); // 特殊处理:初始化操作(允许在任何状态下执行) if (s == 1) { if (IS_VALID(L)) { printf("线性表已经初始化过了!如需重新开始,请先销毁。\n"); } else { status = InitList(L); if (status == OK) { printf("线性表初始化成功!\n"); } else { printf("初始化失败!内存分配失败。\n"); } } continue; } if (!IS_VALID(L)){ printf("线性表尚未初始化或已销毁,请先初始化!\n"); continue; } switch(s){ case 2: cout<<"销毁线性表"<<endl; status = DestroyList(L); if (status == OK){ printf("线性表销毁成功!\n"); }else{ printf("线性表销毁失败!\n"); } break; case 3: cout<<"清空线性表"<<endl; status = ClearList(L); if (status == OK){ printf("线性表清空成功!\n"); }else{ printf("线性表清空失败!\n"); } break; case 4: cout<<"判断线性表是否为空"<<endl; status = ListEmpty(L); if (status == OK){ printf("线性表为空!\n"); }else{ printf("线性表不为空!\n"); } break; case 5: cout<<"求线性表长度"<<endl; printf("线性表当前长度为:%d\n",ListLength(L)); break; case 6: cout<<"获取线性表指定元素位置"<<endl; int i; ElemType e; printf("请输入要获取的元素位置: "); scanf("%d",&i); //读取输入的整数存入i中 status = GetElem(L,i,e); //调用GetElem函数,传入L,i,e if (status == OK){ printf("第%d个元素为:%d\n", i, e); }else{ printf("获取失败,位置无效\n"); } break; case 7:{ //形成局部作用域 cout<<"获取线性表中元素的位置"<<endl; ElemType e; printf("请输入要查找的元素值:"); scanf("%d", &e); int pos = LocateElem(L,e); if(pos != 0){ printf("该元素在第%d位\n", pos); }else{ printf("该元素不存在\n"); } break; } case 8:{ cout<<"求前驱"<<endl; ElemType cur,pre; printf("请输入当前元素值: "); scanf("%d",&cur); status = PriorElem(L,cur,pre); if (status == OK){ printf("前驱元素为:%d\n",pre); }else{ printf("没有前驱或元素不存在\n"); } break; } case 9:{ cout<<"求后继"<<endl; ElemType cur,next; printf("请输入当前元素值: "); scanf("%d",&cur); status = NextElem(L,cur,next); if (status == OK){ printf("后继元素为:%d\n",next); }else{ printf("没有后继或元素不存在\n"); } break; } case 10:{ cout<<"在线性表指定位置插入元素"<<endl; int i; ElemType e; printf("请输入插入位置和值(中间用空格隔开): "); scanf("%d %d",&i, &e); status = ListInsert(L,i,e); if(status == OK){ printf("插入成功!\n"); }else{ printf("插入失败,位置无效\n"); } break; } case 11:{ cout<<"删除线性表指定位置元素"<<endl; int i; ElemType e; printf("请输入要删除的元素位置:"); scanf("%d",&i); status = ListDelete(L,i,e); if (status == OK){ printf("已删除元素:%d\n",e); }else{ printf("删除失败,位置无效!\n"); } break; } case 12:{ cout<<"根据元素值,删除线性表中的元素"<<endl; ElemType e; printf("请输入要删除的元素值: "); scanf("%d",&e); status = DeleteElem(L,e); if(status == OK) printf("删除成功!\n"); else printf("删除失败或元素不存在!\n"); break; } case 13: cout<<"对线性表进行排序"<<endl; SortList(L); printf("线性表已排序!\n"); break; case 14: cout<<"显示线性表"<<endl; ShowList(L); break; case 15:{ cout<<"合并两个非递减有序的线性表"<<endl; SqList La,Lb,Lc; InitList(La); InitList(Lb); int x; printf("请输入La的数据(输入-1结束): "); while(1){ scanf("%d",&x); if (x == -1) break; ListInsert(La,La.length + 1,x); } printf("请输入Lb的数据(输入-1结束): "); while(1){ scanf("%d",&x); if (x == -1) break; ListInsert(Lb,Lb.length + 1,x); } SortList(La); SortList(Lb); //排序确保非递减 MergeList(La,Lb,Lc); printf("合并后的线性表为 "); ShowList(Lc); DestroyList(La); DestroyList(Lb); DestroyList(Lc); //销毁,防止内存泄漏 break; } default: if(s<0){ n = 0; cout<<"程序退出成功,欢迎下次使用~~"<<endl; break; } else cout<<"您输入的指令有误,请重新输入~"<<endl; }//switch }//while }//main 写出整体程序框架是怎么安排(菜单结构的设计)的,另外分别写出上述13个函数的设计思想和思路方法。
最新发布
09-21
你提供的这个程序是一个 **完整的顺序表(线性表)管理系统**,使用 C++ 语法封装了标准的顺序表操作。下面我将从两个方面详细解析: --- ## ✅ 一、整体程序框架与菜单结构设计 ### 🧩 1. 程序总体架构 这是一个典型的 **基于控制台的交互式菜单系统(CLI)**,采用 `switch-case` 结构实现功能选择。 #### 🔹 模块划分: | 类型 | 内容 | |------|------| | 数据结构定义 | `SqList` 结构体,表示顺序表 | | 函数声明与实现 | 封装了 15 个基本操作函数 | | 主函数逻辑 | `main()` 中通过循环 + 菜单驱动用户输入 | --- ### 📋 2. 菜单结构设计 ```cpp while(n) { 显示菜单; 输入操作码 s; if (s == 1 && 未初始化) 初始化; else if (!已初始化) 提示先初始化; else switch(s) { case 2~15: 各项功能处理 default: 错误提示或退出 } } ``` #### ✅ 菜单功能列表: | 编号 | 功能 | 是否需要线性表已初始化 | |------|------|------------------| | 1 | 初始化线性表 L | ❌(允许执行) | | 2 | 销毁线性表 | ✅ | | 3 | 清空线性表 | ✅ | | 4 | 判断是否为空 | ✅ | | 5 | 获取长度 | ✅ | | 6 | 获取指定位置元素 | ✅ | | 7 | 查找元素位置 | ✅ | | 8 | 求前驱 | ✅ | | 9 | 求后继 | ✅ | | 10 | 插入元素 | ✅ | | 11 | 删除指定位置元素 | ✅ | | 12 | 根据值删除元素 | ✅ | | 13 | 排序 | ✅ | | 14 | 显示线性表 | ✅ | | 15 | 合并两个有序表(临时创建 La/Lb) | ✅(但内部独立初始化) | | 负数 | 退出程序 | - | --- ### ⚠️ 安全机制设计亮点: - 使用宏判断状态: ```c #define IS_VALID(L) ((L).elem != NULL) ``` 防止对未初始化或已销毁的表进行非法访问。 - 特殊处理初始化(case 1):即使表已存在也提示不能重复初始化。 - 所有涉及内存的操作都做了判空和异常处理(如 `malloc`, `realloc` 失败时调用 `exit(OVERFLOW)`)。 - `case 15` 中合并完成后主动释放 `La`, `Lb`, `Lc`,防止内存泄漏。 --- ### 🔄 控制流图示意: ``` 开始 ↓ 显示主菜单 ↓ 循环等待用户输入操作码 s ↓ if s == 1 → 尝试初始化(仅一次) else if !IS_VALID(L) → 提示“请先初始化” else → 进入 switch 分支处理对应功能 ↓ default: if s < 0 → n=0 → 结束循环 → 退出 else → 提示错误指令 ``` --- ## ✅ 二、15 个函数的设计思想与思路方法详解 > 注:共 15 个函数,其中 `InitList` 到 `MergeList` 共 14 个 + `ShowList` 已包含在内。 --- ### 1. `Status InitList(SqList &L)` - **目标**:构造一个空的顺序表。 - **思路**: - 使用 `malloc` 分配初始容量为 `LIST_INIT_SIZE` 的数组空间。 - 初始化 `length=0`, `listsize=初始大小`。 - **关键点**: - 返回 `OVERFLOW` 若分配失败(用 `exit()` 终止)。 - 使用引用传递(`&L`),修改外部变量。 --- ### 2. `Status DestroyList(SqList &L)` - **目标**:彻底销毁线性表,释放资源。 - **思路**: - `free(L.elem)` 释放动态数组。 - 将指针置为 `NULL`,避免悬垂指针。 - 重置 `length` 和 `listsize`。 - **优点**:安全释放,符合良好编程习惯。 --- ### 3. `Status ClearList(SqList &L)` - **目标**:清空内容,保留结构。 - **思路**: - 只需设置 `L.length = 0`。 - 不释放内存,可继续使用。 - **区别于 Destroy**:不清除内存,仅逻辑清空。 --- ### 4. `Status ListEmpty(SqList L)` - **目标**:判断是否为空。 - **思路**: - 直接比较 `L.length == 0`。 - 返回 `OK(1)` 表示空,`ERROR(0)` 表示非空。 - **注意**:传值而非引用,因为只读不改。 --- ### 5. `int ListLength(SqList L)` - **目标**:返回当前元素个数。 - **思路**: - 直接返回 `L.length`。 - **简单高效**:O(1) 时间复杂度。 --- ### 6. `Status GetElem(SqList L, int i, ElemType &e)` - **目标**:获取第 `i` 个位置的元素。 - **思路**: - 检查 `i` 是否合法(1 ≤ i ≤ length)。 - 若合法,则 `e = L.elem[i-1]`(下标从0起)。 - **返回值**:成功返回 `OK`,否则 `ERROR`。 --- ### 7. `int LocateElem(SqList L, ElemType e)` - **目标**:查找元素 `e` 的**位序**(从1开始)。 - **思路**: - 遍历 `L.elem[0] ~ L.elem[L.length-1]`。 - 找到第一个相等的元素,返回其位序 `i+1`。 - 未找到返回 `0`。 - **特点**:顺序查找,适用于无序表;若有多个相同元素,只返回第一个。 --- ### 8. `Status PriorElem(SqList L, ElemType cur_e, ElemType &pre_e)` - **目标**:获取某元素的前驱。 - **思路**: - 先调用 `LocateElem(L, cur_e)` 得到位序 `pos`。 - 若 `pos <= 1` 或不存在(`pos==0`),则无前驱 → 返回 `ERROR`。 - 否则取 `L.elem[pos-2]`(即前一个元素)赋给 `pre_e`。 - **局限性**:若有重复元素,只能找到第一个出现位置的前驱。 --- ### 9. `Status NextElem(SqList L, ElemType cur_e, ElemType &next_e)` - **目标**:获取某元素的后继。 - **思路**: - 同样依赖 `LocateElem` 找位置 `pos`。 - 若 `pos==0`(未找到)或 `pos==L.length`(最后一个),返回 `ERROR`。 - 否则取 `L.elem[pos]` 作为后继(`pos-1+1 = pos` 下标)。 - **代码优化建议**:`L.elem[pos]` 更清晰写法是 `L.elem[pos]`。 --- ### 10. `Status ListInsert(SqList &L, int i, ElemType e)` - **目标**:在第 `i` 个位置插入新元素。 - **思路**: - 检查位置有效性(1 ≤ i ≤ L.length+1)。 - 若空间不足(`length >= listsize`),用 `realloc` 扩容 10 个单位。 - 从后往前移动 `[i-1, length-1]` 的元素,腾出位置。 - 插入 `e` 并 `length++`。 - **时间复杂度**:O(n),最坏情况移动全部元素。 - **空间管理**:自动扩容机制提升实用性。 --- ### 11. `Status ListDelete(SqList &L, int i, ElemType &e)` - **目标**:删除第 `i` 个元素,并返回其值。 - **思路**: - 检查 `i` 是否有效。 - 保存 `L.elem[i-1]` 到 `e`。 - 从 `i` 开始,所有后续元素前移一位。 - `length--`。 - **效率**:平均需移动一半元素,O(n)。 --- ### 12. `Status DeleteElem(SqList &L, ElemType e)` - **目标**:根据元素值删除该元素(第一次出现)。 - **思路**: - 调用 `LocateElem(L, e)` 找位置 `pos`。 - 若找不到(`pos==0`),返回 `ERROR`。 - 否则调用 `ListDelete(L, pos, temp)` 删除该位置。 - **复用已有函数**:体现了模块化设计思想。 --- ### 13. `void SortList(SqList &L)` - **目标**:将线性表按升序排序。 - **算法**:冒泡排序(简单稳定) - **实现**: - 外层控制轮数(n-1轮) - 内层比较相邻元素,逆序则交换 - 使用 `swap()` 函数(STL 或内置) - **时间复杂度**:O(n²),适合小数据量 - **改进方向**:可用快速排序或调用 `qsort` --- ### 14. `void ShowList(SqList L)` - **目标**:打印线性表内容,逗号分隔。 - **思路**: - 若为空输出提示。 - 遍历每个元素,输出 `%d`。 - 在非末尾元素后加 `,`。 - **格式友好**:输出形如 `1,2,3,4` --- ### 15. `Status MergeList(SqList La, SqList Lb, SqList &Lc)` - **目标**:合并两个**非递减有序**表 → 新有序表(去重) - **思路**: 1. 使用归并排序中的“归并”思想: - 双指针 `i,j` 分别指向 `La`, `Lb` 起始位置 - 比较 `La[i]` 和 `Lb[j]`,较小者放入 `Lc[k++]` 2. 处理剩余元素 3. 对合并后的 `Lc` 做**有序去重**: - 双指针法:`writeIndex` 记录写入位置,跳过重复项 - 最终 `Lc.length = writeIndex` - **时间复杂度**:O(m+n) - **空间复杂度**:O(m+n) --- ## ✅ 总结:整体设计思想 | 设计原则 | 实现体现 | |---------|----------| | **模块化** | 每个功能封装成独立函数 | | **安全性** | 检查边界、判空、防越界、防重复初始化 | | **可扩展性** | 支持动态扩容(realloc)、支持合并等复合操作 | | **用户体验** | 菜单清晰、提示明确、支持重复操作 | | **内存管理严谨** | 初始化/销毁配对,无内存泄漏(尤其 case 15) | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值