风也温柔

计算机科学知识库

数据结构next函数用法

  1 算法的复杂度 1.1大O复杂度表示法

  公式:

  数据结构next函数_r语言data函数列出数据_数据表单统计函数

  T(n)表示代码执行的时间; n表示数据规模的大小; f(n) 表示每行代码执行的次数总和。因为这是一个公式, 所以用f(n)来表示。公式中的O,表示代码的执行时间T(n)与f(n)表达式成正比。

  所以,第一个例子中的T(n) = O(2n+2),第二个例子中的T(m) = 0(2n2 +2n+3)。这就是大O时间复杂度表示法。大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度( time ),简称时间复杂度。

  当n很大时,你可以把它想象成10000、。 而公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个最大量级就可以了,如果用大O表示法表示刚讲的那两段代码的时间复杂度,就可以记为: T(n) = O(n); T(n)= 0(n2)。

  1.2.复杂度分析法则

  1)单段代码看高频:比如循环。

  2)多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。

  3)嵌套代码求乘积:比如递归、多重循环等

  4)多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。

  1.3 时间复杂度分析

  1.4 几种常见时间复杂度实例分析

  数据结构next函数_r语言data函数列出数据_数据表单统计函数

  多项式阶:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括,

  O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n^2)(平方阶)、O(n^3)(立方阶)

  非多项式阶:随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括,

  O(2^n)(指数阶)、O(n!)(阶乘阶)

  常量级时间复杂度,只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。

  <pre class="has">`i=1;
while(inext:出队时, head = head->next.

  r语言data函数列出数据_数据结构next函数_数据表单统计函数

  循环队列:

  我们刚才用数组来实现队列的时候,在tail==n时,会有数据搬移操作,这样入队操作性能就会受到影响。那有没有办法能够避免数据搬移呢?我们来看看循环队列的解决思路。循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相,连,板成了一个环。我画了一张图,你可以直观地感受一下。

  数据表单统计函数_数据结构next函数_r语言data函数列出数据

  我们可以看到,图中这个队列的大小为8,当前head-4, tail-7,当有一个新的元素a入队时, .我们放入下标为7的位置。但这个时候,我们并不把tail更新为8,而是将其在环中后移一位,到下标为0的位置。当再有一个元素b入队时,我们将b放入下标为0的位置,然后tail加1更新为1,所以,在a, b依次入队之后,循环队列中的元素就变成了下面的样子:

  数据表单统计函数_数据结构next函数_r语言data函数列出数据

  队列为空的判断条件是head == tail,但队列满的判断条件就稍微有点复杂了。我画了一张队列满的图,你可以看一下,试着总结一下规律,

  数据结构next函数_r语言data函数列出数据_数据表单统计函数

  就像我图中画的队满的情况, tail=3, head-4, n=8,所以总结一下规律就是: (3+1)%8-4,多画几张队满的图,你就会发现,当队满时, (tail+1)%n=head..你有没有发现,当队列满时,图中的tail指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。

  解决浪费一个存储空间的思路:定义一个记录队列大小的值size,当这个值与数组大小相等时,表示队列已满,当tail达到最底时,size不等于数组大小时,tail就指向数组第一个位置。当出队时,size—,入队时size++

  阻塞队列和并发队列(应用比较广泛)

  阻塞队列其实就是在队列基础上增加了阻塞操作。

  简单来说,就是在队列为空的时候,从队头取数 , 据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。

  r语言data函数列出数据_数据结构next函数_数据表单统计函数

  你应该已经发现了,上述的定义就是一个"生产者-消费者模型" !是的,我们可以使用阻塞队列,轻松实现一个"生产者-消费者模型" !这种基干阴寒队列实现的"生产者-消费者模型" ,可以有效地协调生产和消费的速度。当"生产 , 者"生产数据的速度过快, "消费者"来不及消费时,存储数据的队列很快就会满了。这个时候,生产者就阻塞等待,直到"消费者"消费了数据, "生产者"才会被唤醒继续"生产而且不仅如此,基于阻塞队列,我们还可以通过协调"生产者"和"消费者"的个数,来提高数据,的处理效率。比如前面的例子,我们可以多配置几个"消费者" ,来应对一个"生产者"

  小结:

  队列最大的特点就是先进先出,主要的两个操作是入队和出队。

  它既可以用数组来实现,也可以用链表来实现。用数组实现的叫顺序队列,用链表实现的叫链式队列。

  长在数组实现队列的时候,会有数据搬移操作,要想解决数据搬移的问题,我们就,需要像环一样的循环队列。要想写出没有bug的循环队列实现代码,关键要确定好队空和队满的,判定条件。

  阻塞队列、并发队列,底层都还是队列这种数据结构,只不过在之上附加了很多其他功能。阻塞队列就是入队、出队操作可以阴寒,并发队列就是队列的操作多线程安全。

  4、递归算法

  一、什么是递归?

  1.递归是一种非常高效、简洁的编码技巧,一种应用非常广泛的算法,比如DFS深度优先搜索、前中后序二叉树遍历等都是使用递归。

  2.方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。

  3.基本上,所有的递归问题都可以用递推公式来表示,比如

  f(n) = f(n-1) + 1;

  f(n) = f(n-1) + f(n-2);

  f(n)=n*f(n-1);

  二、为什么使用递归?递归的优缺点?

  1.优点:代码的表达力很强,写起来简洁。

  2.缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。

  三、什么样的问题可以用递归解决呢?

  一个问题只要同时满足以下3个条件,就可以用递归来解决:

  1.问题的解可以分解为几个子问题的解。何为子问题?就是数据规模更小的问题。

  2.问题与子问题,除了数据规模不同,求解思路完全一样

  3.存在递归终止条件

  四、如何实现递归?

  1.递归代码编写

  写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。

  2.递归代码理解

  对于递归代码,若试图想清楚整个递和归的过程,实际上是进入了一个思维误区。

  那该如何理解递归代码呢?如果一个问题A可以分解为若干个子问题B、C、D,你可以假设子问题B、C、D已经解决。而且,你只需要思考问题A与子问题B、C、D两层之间的关系即可,不需要一层层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。

  因此,理解递归代码,就把它抽象成一个递推公式数据结构next函数,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

  递归的关键是终止条件

  五、递归常见问题及解决方案

  1.警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。

  2.警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。

  六、如何将递归改写为非递归代码?

  笼统的讲,所有的递归代码都可以改写为迭代循环的非递归写法。如何做?抽象出递推公式、初始值和边界条件,然后用迭代循环实现。

  5、排序

  一、排序方法与复杂度归类

  (1)几种最经典、最常用的排序方法:冒泡排序、插入排序、选择排序、快速排序、归并排序、计数排序、基数排序、桶排序。

  数据结构next函数_数据表单统计函数_r语言data函数列出数据

  (2)复杂度归类

  冒泡排序、插入排序、选择排序 O(n^2)

  快速排序、归并排序 O(nlogn)

  计数排序、基数排序、桶排序 O(n)

  二、如何分析一个“排序算法”?

  算法的执行效率

  1. 最好、最坏、平均情况时间复杂度。

  2. 时间复杂度的系数、常数和低阶。

  3. 比较次数,交换(或移动)次数。

  排序算法的稳定性

  1. 稳定性概念:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

  2. 稳定性重要性:可针对对象的多种属性进行有优先级的排序。

  3. 举例:给电商交易系统中的“订单”排序,按照金额大小对订单数据排序,对于相同金额的订单以下单时间早晚排序。用稳定排序算法可简洁地解决。先按照下单时间给订单排序数据结构next函数,排序完成后用稳定排序算法按照订单金额重新排序。

  排序算法的内存损耗

  原地排序算法:特指空间复杂度是O(1)的排序算法。

  常见的排序算法:

  数据结构next函数_数据表单统计函数_r语言data函数列出数据

  冒泡排序

  数据表单统计函数_数据结构next函数_r语言data函数列出数据

  冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足就让它俩互换。

  代码:

  <pre class="has"> public int[] bubbleSort(int[] a) {

    int n = a.length;
    if (na[j+1]) {//
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
                flag = true;//表示有数据交换
            }
            if (!flag) {
                break; //没有数据交换(说明已排好序无需再进行冒泡),提前退出
            }
        }
    }
    return a;
}`</pre>

  四、插入排序

  数据结构next函数_数据表单统计函数_r语言data函数列出数据

  插入排序将数组数据分成已排序区间和未排序区间。初始已排序区间只有一个元素,即数组第一个元素。在未排序区间取出一个元素插入到已排序区间的合适位置,直到未排序区间为空。

  代码:

  <pre class="has">` public int[] insertionSort(int[] a) {

    int n = a.length;
    if (n=0; j--) {
            if (a[j] > value) {
                a[j+1] = a[j];//移动数据
            }else {
                break;
            }
        }
        a[j+1] = value;//插入数据
    }
    
    return a;
}`</pre>

  五、选择排序

  数据结构next函数_数据表单统计函数_r语言data函数列出数据

  选择排序将数组分成已排序区间和未排序区间。初始已排序区间为空。每次从未排序区间中选出最小的元素插入已排序区间的末尾,直到未排序区间为空。

  代码:

  <pre class="has">`public int[] selectionSort(int[] a) {

    int n = a.length;
    
    for (int i = 0; i < a.length - 1; i++) {
        for (int j = i+1; j < a.length; j++) {
            //交换

<p>数据结构next函数_r语言data函数列出数据_数据表单统计函数

            if (a[i] > a[j]) {
                int temp = a[i];
                a[i] = a[j];
                a[j] = temp;
            }
        }
    }
    
    return a;
}`</pre></p>

  六、归并排序

  如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

  实现思路:

  数据结构next函数_数据表单统计函数_r语言data函数列出数据

  merge-sort(p...r)表示,给下标从p到r之间的数组排序。我们将这个排序问题转化为了两个子问 ,题, (p...q)和merge-sort(q+1..r),其中下标q等于p和r的中间位置,也就是, (p+r)/2,当下标从p到q和从q+1到r这两个子数组都排好序之后,我们再将两个有序的子数组合并在一起,这样下标从p到r之间的数据就也排好序了。

  代码:

<p><pre class="has"> // 归并排序算法, a是数组,n表示数组大小
public static void mergeSort(int[] a, int n) {

mergeSortInternally(a, 0, n-1);

}
// 递归调用函数
private static void mergeSortInternally(int[] a, int p, int r) {

// 递归终止条件
if (p >= r) return;
// 取p到r之间的中间位置q
int q = (p+r)/2;
// 分治递归
mergeSortInternally(a, p, q);
mergeSortInternally(a, q+1, r);
// 将A[p...q]和A[q+1...r]合并为A[p...r]
merge(a, p, q, r);

}
private static void merge(int[] a, int p, int q, int r) {

int i = p;
int j = q+1;
int k = 0; // 初始化变量i, j, k
int[] tmp = new int[r-p+1]; // 申请一个大小跟a[p...r]一样的临时数组

// 1 排序
while (i