八种常见顺序存储的算法

简介: 八种常见顺序存储的算法

1、线性枚举

1)问题描述

2)动图演示

3)示例说明

 蓝色的数据代表的是数组数据,红色的数据代表当前枚举到的数据,这样就可以遍历所有的数据进行逻辑处理了。


4)算法描述

 遍历数组,进行条件判断,条件满足则执行逻辑。这里的条件就是 枚举到的数 是否小于 当前最小值,执行逻辑为 将 当前枚举到的数 赋值给 当前最小值;


5)源码详解

int findMin(int* nums, int numsSize){
    int i, min = 100000;
    for(i = 0; i < numsSize; ++i) {     // (1)
        if(nums[i] < min) {             // (2)
            min = nums[i];
        }
    }
    return min;                         // (3)
}
  • (1) 遍历数组中所有的数;
  • (2) 如果 当前枚举到的数 比记录的变量min小,则将它赋值给min;否则,不做任何处理;
  • (3) 最后,min中存储的就是整个数组的最小值。

2、前缀和差分

1)问题描述

2)动图演示

3)样例分析

 如上图所示,只需要记录一个前缀和,然后就可以通过一次减法将区间的值计算出来。时间复杂度 O(1)。这种就是差分的思想。


原理:


sum[r] = a[1] + a[2] + a[3] + a[l-1] + a[l] + a[l + 1] ...... a[r];


sum[l - 1] = a[1] + a[2] + a[3] + a[l - 1];


sum[r] - sum[l - 1] = a[l] + a[l + 1] + ......+ a[r];


4)算法描述

 第一个枚举,利用一个数组sum,存储前 i 个元素的和。

 第二个枚举,读入 m 组数据 l,r,对每组数据,通过 O(1) 获取答案,即sum[r] - sum[l - 1]。


5)源码详解

int sum[maxn];
int* prefixSum(int* nums, int numsSize, int m, int *l, int *r){
    int i;
    int *ret;
    for(i = 0; i < numsSize; ++i) {
        sum[i] = nums[i];
        if(i) 
            sum[i] += sum[i-1];                 // (1) 
    }
    ret = (int *) malloc( m * sizeof(int) );    // (2) 
    for(i = 0; i < m; ++i) {
      int leftsum = l[i] == 0 ? 0 : sum[l[i]-1]; // (3) 
      int rightsum = sum[r[i]];
      ret[i] = rightsum - leftsum;            // (4) 
    }
    return ret;
}
  • (1) 计算前缀和;
  • (2) 需要返回的数组;
  • (3) 这里是为了防止数组下标越界;
  • (4) 核心 O(1) 的差分计算;


3、双指针

1)问题描述

2)动图演示

3)样例说明

 维护两个指针 i 和 j,区间 [i,j] 内的子串,应该时刻保持其中所有字符不重复,一旦发现重复字符,就需要自增 i(即执行 i = i + 1);否则,执行 j = j + 1,直到 j 不能再增加为止。

 过程中,记录合法情况下 j − i + 1 的最大值。


4)算法描述

 如上文所述,这种利用问题特性,通过两个指针,不断调整区间,从而求出问题最优解的算法就叫 “尺取法”,由于利用的是两个指针,所以又叫 “双指针” 算法。

 这里 “尺” 的含义,主要还是因为这类问题,最终要求解的都是连续的序列(子串),就好比一把尺子一样,故而得名。


算法描述如下:

 1)初始化 i = 0, j = i − 1,代表一开始 “尺子” 的长度为 0;

 2)增加 “尺子” 的长度,即 j = j + 1;

 3)判断当前这把 “尺子” [i,j] 是否满足题目给出的条件:

   3.a)如果不满足,则减小 “尺子” 长度,即 i = i + 1,回到 3);

   3.b)如果满足,记录最优解,回到 2);

  • 上面这段文字描述的比较官方,其实这个算法的核心,只有一句话:满足条件时,j++;不满足条件时,i++;
  • 如图所示,当区间 [i,j] 满足条件时,用蓝色表示,此时 j 自增;反之闪红,此时 i 自增。

5)源码详解

int getmaxlen(int n, char *str, int& l, int& r) {
    int ans = 0, i = 0, j = -1, len;   // 1)
    memset(h, 0, sizeof(h));           // 2)
    while (j++ < n - 1) {              // 3)
        ++h[ str[j] ];                 // 4)
        while (h[ str[j] ] > 1) {      // 5)
            --h[ str[i] ];
            ++i;
        }
        len = j - i + 1;              
        if(len > ans)                  // 6)
            ans = len, l = i, r = j;
    }
    return ans;
}
  • 1)初始化 i = 0, j = -1,代表 s[i:j] 为一个空串,从空串开始枚举;
  • 2)需要维护一个哈希表,哈希表记录的是当前枚举的区间 s[i:j] 中每个字符的个数;
  • 3)只推进子串的右端点;
  • 4)在哈希表中记录字符的个数;
  • 5)当 h[ str[j] ] > 1满足时,代表出现了重复字符str[j],这时候左端点 i 推进,直到没有重复字符为止;
  • 6)记录当前最优解的长度 j - i + 1,更新;
  • 这个算法执行完毕,我们就可以得到最长不重复子串的长度为 ans,并且 i 和 j 这两个指针分别只自增 n 次,两者自增相互独立,是一个相加而非相乘的关系,所以这个算法的时间复杂度为 O(n) 。


4、二分枚举

1)问题描述

7df7139a7717122d4ea9a4ea24b899de_2bb2c754be54452e8f9fb854dab600e0.png

2)动图演示

image.gif


3)样例说明

 需要找值为 5 的这个元素。

 黄色箭头代表都是左区间端点 l,红色箭头代表右区间端点 r。蓝色的数据为数组数据,绿色的数字代表的是数组下标,初始化 l = 0,r = 7,由于数组有序,则可以直接折半,令 mid =(l+r)/2=3,则 55 一定落入区间 [0,3],这时候令 r = 3,继续执行,直到 l > r 结束迭代。

 最后,当 mid = 2 时,找到数据 5。


4)算法描述

 a)令初始情况下,数组下标从 0 开始,且数组长度为 n,则定义一个区间,它的左端点是      l = 0,右端点是 r = n−1;

 b)生成一个区间中点 mid = (l+r)/2,并且判断 mid 对应的数组元素和给定的目标值的大小关系,主要有三种:

   b.1)目标值 等于 数组元素,直接返回 mid;

   b.2)目标值 大于 数组元素,则代表目标值应该出现在区间 [mid+1,r],迭代左区间端点:l=mid+1;

   b.3)目标值 小于 数组元素,则代表目标值应该出现在区间 [l,mid−1],迭代右区间端点:r=mid−1;

 c)如果这时候 l>r,则说明没有找到目标值,返回 −1;否则,回到 b)继续迭代。


5)源码详解

int search(int *nums, int numsSize, int target) {
    int l = 0, r = numsSize - 1;         // (1)
    while(l <= r) {                      // (2)
        int mid = (l + r) >> 1;          // (3)
        if(nums[mid] == target) {   
            return mid;                  // (4)
        }else if(target > nums[mid]) {
            l = mid + 1;                 // (5)
        }else if(target < nums[mid]) {
            r = mid - 1;                 // (6)
        }
    }
    return -1;                           // (7)
}
  • (1) 初始化区间左右端点;
  • (2) 一直迭代左右区间的端点,直到 左端点 大于 右端点 结束;
  • (3) >> 1等价于除 2,也就是这里mid代表的是l和r的中点;
  • (4) nums[mid] == target表示正好找到了这个数,则直接返回下标mid;
  • (5) target > nums[mid]表示target这个数在区间 [���+1,�][mid+1,r] 中,所以才有左区间赋值如下:l = mid + 1;
  • (6) target < nums[mid]表示target这个数在区间 [�,���−1][l,mid−1] 中,所以才有右区间赋值如下:r = mid - 1;
  • (7) 这一步呼应了 (2),表示这不到给定的数,直接返回 -1;

5、三分枚举

  三分枚举 类似 二分枚举 的思想,也是将区间一下子砍掉一块基本完全不可能的块,从而减小算法的时间复杂度。只不过 二分枚举 解决的是 单调性 问题。而 三分枚举 解决的是 极值问题。

6、插入排序

1)问题描述

  给定一个 n 个元素的数组,数组下标从 0 开始,采用「 插入排序 」将数组按照 「升序」排列。

2)动图演示

3)样例说明

图示 含义
蓝色柱形 代表尚未排好序的数
绿色柱形 代表正在执行 比较 和 移动 的数
橙色柱形 代表已经排好序的数
红色柱形 代表待执行插入的数

 我们看到,首先需要将 「第二个元素」 和 「第一个元素」 进行 「比较」,如果 前者 小于等于 后者,则将 后者 进行向后 「移动」,前者 则执行插入;

 然后,进行第二轮「比较」,即 「第三个元素」 和 「第二个元素」、「第一个元素」 进行 「比较」, 直到 「前三个元素」 保持有序 。

 最后,经过一定轮次的「比较」 和 「移动」之后,一定可以保证所有元素都是 「升序」 排列的。

4)算法描述

整个算法的执行过程分以下几步:

 1) 循环迭代变量 i=1→n−1;

 2) 每次迭代,令 x=a[i],j=i−1,循环执行比较 x 和 a[j],如果产生 x≤a[j] 则执 行 a[j+1]=a[j]。然后执行j=j+1,继续执行 2);否则,跳出循环,回到 1)。


5)源码详解

#include <stdio.h>
 
int a[1010];
 
void Input(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
}
 
void Output(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        if(i)
            printf(" ");
        printf("%d", a[i]);
    }
    puts("");
}
 
void InsertSort(int n, int *a) {       // (1)
    int i, j; 
    for(i = 1; i < n; ++i) {
        int x = a[i];                  // (2)
        for(j = i-1; j >= 0; --j) {    // (3)
            if(x <= a[j]) {            // (4)
                a[j+1] = a[j];         // (5)
            }else
                break;                 // (6)
        }
        a[j+1] = x;                    // (7)
    }
} 
 
int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        Input(n, a);
        InsertSort(n, a);
        Output(n, a);
    }
    return 0;
} 
  • (1) void InsertSort(int n, int *a)为 插入排序 的实现,代表对a[]数组进行升序排序。
  • (2) 此时a[i]前面的 i-1个数都认为是排好序的,令x = a[i];
  • (3) 逆序的枚举所有的已经排好序的数;
  • (4) 如果枚举到的数a[j]比需要插入的数x大,则当前数往后挪一个位置;
  • (5) 执行挪位置的 �(1)O(1) 操作;
  • (6) 否则,跳出循环;
  • (7) 将x插入到合适位置;

7、选择排序

1)问题描述

  给定一个 �n 个元素的数组,数组下标从 00 开始,采用「 选择排序 」将数组按照 「升序」排列。

2)动图演示

3)样例说明

图示 含义
蓝色柱形 代表尚未排好序的数
绿色柱形 代表正在执行 比较 的数
橙色柱形 代表已经排好序的数
红色柱形 有两种:1、记录最小元素 2、执行交换的元素

 我们发现,首先从 「第一个元素」 到 「最后一个元素」 中选择出一个 「最小的元素」,和 「第一个元素」 进行 「交换」;

 然后,从 「第二个元素」 到 「最后一个元素」 中选择出一个 「最小的元素」,和 「第二个元素」 进行 「交换」。

 最后,一定可以保证所有元素都是 「升序」 排列的。


4)算法描述

整个算法的执行过程分以下几步:

 1) 循环迭代变量 i=0→n−1;

 2) 每次迭代,令 min=i,j=i+1;

 3) 循环执行比较 a[j] 和 a[min],如果产生 a[j]<a[min] 则执行 min=j。执行 j=j+1,继续执行这一步,直到 j==n;

 4) 交换 a[i] 和 a[min],回到 1)。


5)源码详解

#include <stdio.h>
 
int a[1010];
 
void Input(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
}
 
void Output(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        if(i)
            printf(" ");
        printf("%d", a[i]);
    }
    puts("");
}
 
void Swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
 
void SelectionSort(int n, int *a) {  // (1)
    int i, j;
    for(i = 0; i < n - 1; ++i) {     // (2)
        int min = i;                 // (3)
        for(j = i+1; j < n; ++j) {   // (4)
            if(a[j] < a[min]) {
                min = j;             // (5)
            }
        }
        Swap(&a[i], &a[min]);        // (6) 
    }
}
 
int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        Input(n, a);
        SelectionSort(n, a);
        Output(n, a);
    }
    return 0;
} 
  • (1) void SelectionSort(int n, int *a)为选择排序的实现,代表对a[]数组进行升序排序。
  • (2) 从首元素个元素开始进行 n−1 次跌迭代。
  • (3) 首先,记录min代表当前第 i 轮迭代的最小元素的下标为 i。
  • (4) 然后,迭代枚举第 i+1 个元素到 最后的元素。
  • (5) 选择一个最小的元素,并且存储下标到 min中。
  • (6) 将 第 i 个元素 和 最小的元素 进行交换。

8、冒泡排序

1)问题描述

  给定一个 n 个元素的数组,数组下标从 0 开始,采用「 冒泡排序 」将数组按照 「升序」排列。

2)动图演示

3)样例说明

图示 含义
蓝色柱形 代表尚未排好序的数
绿色柱形 代表正在执行比较的两个数
黄色柱形 代表已经排好序的数

 我们看到,首先需要将 「第一个元素」 和 「第二个元素」 进行 「比较」,如果 前者 大于 后者,则进行 「交换」,然后再比较 「第二个元素」 和 「第三个元素」 ,以此类推,直到 「最大的那个元素」 被移动到 「最后的位置」 。

 然后,进行第二轮「比较」,直到 「次大的那个元素」 被移动到 「倒数第二的位置」 。

 最后,经过一定轮次的「比较」 和 「交换」之后,一定可以保证所有元素都是 「升序」 排列的。


4)算法描述

整个算法的执行过程分以下几步:

 1) 循环迭代变量 i=0→n−1;

 2) 每次迭代,令 j=i,循环执行比较 a[j] 和 a[j+1],如果产生 a[j]>a[j+1] 则交换两者的值。然后执行 j=j+1,这时候对 j 进行判断,如果 j≥n−1,则回到 1),否则继续执行 2)。


5)源码详解

#include <stdio.h>
 
int a[1010];
 
void Input(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
}
 
void Output(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        if(i)
            printf(" ");
        printf("%d", a[i]);
    }
    puts("");
}
 
void Swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
 
void BubbleSort(int n, int *a) {             // (1)
    bool swapped;
    int last = n;
    do {
        swapped = false;                     // (2)
        for(int i = 0; i < last - 1; ++i) {  // (3)
            if(a[i] > a[i+1]) {              // (4)
                Swap(&a[i], &a[i+1]);        // (5)
                swapped = true;              // (6)
            }
        }
        --last;
    }while (swapped);
} 
 
int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        Input(n, a);
        BubbleSort(n, a);
        Output(n, a);
    }
    return 0;
} 
  • (1) void BubbleSort(int n, int *a)为冒泡排序的实现,代表对a[]数组进行升序排序。
  • (2) swapped标记本轮迭代下来,是否有元素产生了交换。
  • (3) 每次冒泡的结果,会执行last的自减,所以待排序的元素会越来越少。
  • (4) 如果发现两个相邻元素产生逆序,则将它们进行交换。保证右边的元素一定不比左边的小。
  • (5) swap实现了元素的交换,这里需要用&转换成地址作为传参。
  • (6) 标记更新。一旦标记更新,则代表进行了交换,所以下次迭代必须继续。
相关文章
|
5月前
|
存储 算法
【操作系统】虚拟存储管理-页面置换算法
【操作系统】虚拟存储管理-页面置换算法
440 0
|
5月前
|
存储 监控 NoSQL
Redis处理大量数据主要依赖于其内存存储结构、高效的数据结构和算法,以及一系列的优化策略
【5月更文挑战第15天】Redis处理大量数据依赖内存存储、高效数据结构和优化策略。选择合适的数据结构、利用批量操作减少网络开销、控制批量大小、使用Redis Cluster进行分布式存储、优化内存使用及监控调优是关键。通过这些方法,Redis能有效处理大量数据并保持高性能。
80 1
|
2月前
|
存储 缓存 算法
深入解析B树:数据结构、存储结构与算法优势
深入解析B树:数据结构、存储结构与算法优势
|
2月前
|
存储 算法
【数据结构与算法】队列(顺序存储)
【数据结构与算法】队列(顺序存储)
17 0
|
4月前
|
存储 算法 Java
Java查找算法概览:二分查找适用于有序数组,通过比较中间元素缩小搜索范围;哈希查找利用哈希函数快速定位,示例中使用HashMap存储键值对,支持多值关联。
【6月更文挑战第21天】Java查找算法概览:二分查找适用于有序数组,通过比较中间元素缩小搜索范围;哈希查找利用哈希函数快速定位,示例中使用HashMap存储键值对,支持多值关联。简单哈希表实现未涵盖冲突解决和删除操作。
43 1
|
4月前
|
存储 算法
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
26 0
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
|
4月前
|
存储 算法 Java
Java数据结构与算法:用于高效地存储和检索字符串数据集
Java数据结构与算法:用于高效地存储和检索字符串数据集
|
5月前
|
存储 算法 PHP
开发一款扑克游戏,请给出一套洗牌算法,公平的洗牌并将洗好的牌存储在一个整形数组里?
开发一款扑克游戏,请给出一套洗牌算法,公平的洗牌并将洗好的牌存储在一个整形数组里?
35 1
开发一款扑克游戏,请给出一套洗牌算法,公平的洗牌并将洗好的牌存储在一个整形数组里?
|
5月前
|
存储 机器学习/深度学习 人工智能
【408数据结构与算法】—数组和特殊矩阵的压缩存储(二十五)
【408数据结构与算法】—数组和特殊矩阵的压缩存储(二十五)
|
存储 算法 数据中心
带你读《存储漫谈:Ceph原理与实践》——2.2.2 CRUSH 算法因子
带你读《存储漫谈:Ceph原理与实践》——2.2.2 CRUSH 算法因子