精华内容
下载资源
问答
  • //将删除落实到数据库删除 ??? //当删除次数等于选中复选框传过来的个数时,删除成功,否则失败,分别返回两种页面 } } if (m==Id.length) { ...
  • //原有数据保存起来 //输出表格 document.write("<table border=2 align='center' rules=all id=student>"); document.write("<tr>"); //单元格标题开始 for(var i=0;i;i++) { document.write("<th>"+title...
  • 数据结构不迷茫

    2020-11-05 22:49:24
    其实每种数据结构都是为了满足某种场景的需求,都是有着某种内在的联系的,今天我们尝试进行梳理和总结。此外,在我们对这些数据结构进行研究的时候,主要关注于其评价指标,包括查找,删除,插入的时间复杂度。 ...

    前言

    HashMap是怎么实现的?为什么JDK8之后要换成红黑树?Mysql的索引为什么要用B+树?

    这些问题在面试中是经常被问到的。今天抽空把这些数据结构进行总结。其实每种数据结构都是为了满足某种场景的需求,都是有着某种内在的联系的,今天我们将尝试进行梳理和总结。此外,在我们对这些数据结构进行研究的时候,主要关注于其评价指标,包括查找,删除,插入的时间复杂度。

    这里解释下,我只是对其进行了梳理和总结,里面的配图是在网上找的,会在后面注明来源。

    数组和链表

    数组和链表是我们最先接触到的数据结构,我们先来分析下

    数组 链表
    查找 O(1) O(n)
    插入 O(n) O(1)
    删除 O(n) O(1)

    注:我们一般设置一个哨兵节点,这样在处理头结点的删除和插入的时候不至于进行特殊处理。

    上面的链表说的是单链表,除此之外,我们还需要了解双向链表和循环链表

    栈和队列

    这里需要注意,栈和队列本质上是用数组/链表来实现的,不过二者是操作受限的线性表,其在特定的场景下可以发挥特有的作用。

    跳表

    跳表是什么?我们为什么需要跳表呢?

    我们知道,二分查找的时间复杂度为lgn,性能是很好的,但是其只能局限在数组中,链表是不能使用二分查找的。那么有没有办法在链表中实现二分查找的特性呢?

    如果数据是有序的,还是需要O(n)的时间复杂度。

    image-20201104215838708

    那怎么提高查找效率呢?每两个结点提取一个结点到上一级。

    image-20201104220146993

    类推:

    image-20201104220326454

    所以,当链表的长度 n 比较大时,在构建索引之后,查找效率的提升就会非常明显。直观上,我们可以看到查找的效率提高了提高,下面让我们来具体分析提高的效率。

    假设链表的长度为n,那么第一级索引的节点个数为n/2,第二级为n/4,那么第k级索引的节点个数为n/(2^k)。我们知道,最高的索引有2个节点,则k=lgn-1。在我们查询数据的时候,如果每一层需要查找m个节点,则在跳表中查询一个数据的时间复杂度就是 O(m*logn)。

    且m=3,则时间复杂度为O(lgn)。

    原因如下:假设我们要查找的数据是 x,在第 k 级索引中,我们遍历到 y 结点之后,发现 x 大于 y,小 于后面的结点 z,所以我们通过 y 的 down 指针,从第 k 级索引下降到第 k-1 级索引。在 第 k-1 级索引中,y 和 z 之间只有 3 个结点(包含 y 和 z),所以,我们在 K-1 级索引中 最多只需要遍历 3 个结点,依次类推,每一级索引都最多只需要遍历 3 个结点。

    image-20201104224116884

    我们可以看到,通过建立索引,我们实现了快速查找,代价了浪费了空间,空间复杂度为O(n)。其实在实际的软件开发中,链表中存储的是比较大的对象,但是索引中存储的是id,所以相比较之下是可以忽略的。

    下面我们来分析插入和删除的时间复杂度,先给结论,也是O(lgn)。

    image-20201104225433740

    我们还需要注意的是要及时更新索引,红黑树是通过左右旋来保持稳定的,而跳表是通过随机函数来保存平衡性的。

    image-20201104230124109

    hashmap

    我们知道,数组可以在O(1)的时间复杂度内完成查询,但是需要O(n)的时间复杂度完成插入和删除,而链表正好相反,那么我们是否可以综合利用数组和链表的优势,这就是hashmap。

    image-20201105102651585

    二叉查找树

    前面我们看到,hashmap是十分高效的,但是其问题也是比较明显的。

    1. Hashmap中的数据是无序存储的,若要输出有序数据,则需要先进行排序,而二叉查找树直接中序遍历即可;
    2. Hashmap扩容耗时多;
    3. Hashmap在hash冲突的时候耗时多;
    4. Hashmap的构造复杂,需要考虑散列函数的设计、冲突解决办法、扩容、缩容等问题,而二叉查找树只需要考虑平衡性问题;

    所以综合以上的考量,在某些场景下,我们需要二叉查找树,其查找的时间复杂度为O(logn)。

    平衡二叉树和红黑树

    前面我们学到二叉查找树在某些情况下可能会失衡,即退化为链表,时间复杂度降低为O(n),所以我们需要对二叉查找树进行平衡。

    最先被使用的是AVL平衡二叉查找树,其首先符合二叉查找树的概念,然后左右子树的高度差不超过1。这样就可以很好的保证查找的时间复杂度了。但是其缺点也是很明显的,需要不断的对整棵树进行调整。

    image-20201105132230229

    这个时候我们就引入了红黑树:左右子树的高度差不超过1倍。红黑树的高度,是2logn。其查找,插入,删除的时间复杂度都是O(logn)。

    image-20201105132916331

    我们再来看另外一种特殊的二叉树:堆。

    我们知道堆是一种完全二叉树,往往采用数据里存储。堆在很多地方都有应用,比如

    • 优先级队列:用在赫夫曼编码、图的最短路径、最小生成树算法
    • TOPK问题
    • 求动态数据中的中位数

    此外,堆在排序算法中的表现也是很好的,时间复杂度是O(logn),空间复杂度为O(1),从这个角度来讲,是要比快排和归并排序都要优秀的,但是工程中更常见的排序算法依然是快排的,那这是为什么呢?主要从2个角度来考虑:

    1. 堆排序数据访问的方式没有快速排序友好。CPU的空间局部性原理。快排中,数据是顺序访问的,而堆中是跳着访问的。
    2. 同样的数据,堆排序的交换次数多余快排。

    B树和B+树

    这里要从mysql讲起,使用mysql来查询数据,我们比较关注的两个点是等值查询和范围查询,如:

    select * from user where id=1234;
    select * from user where id > 1234 and id < 2345

    我们前面学到的数据结构中:

    • 散列表:等值查询时间复杂度为O(1),但可能存在hash冲突的问题,且范围查询不支持;
    • 二分查找树:可能失衡
    • 平衡二叉树(AVL,红黑树):范围查询仍然不方便,且树的高度为logn;
    • 跳表:插入、查找、删除数据的时间复杂度都为logn,同时支持区间查询,但是logn的时间复杂度仍然过高。且B+树提出于1972年,跳表提出于1989年,所以B+树才是爸爸。

    综上,我们必须要降低树的高度,所以肯定是多叉树。

    • B树:

    image-20201105202110991

    • B+树

    image-20201105202321880

    我们来对比下B树和B+树:

    B B+
    存储结构 每个节点存数据 叶子节点存数据,非叶子节点存储索引。所以可以存放更多的数据
    查找性能 不稳定 稳定,必须查找到叶子节点
    范围查询 不支持 支持

    所以,mysql的索引选择了B+树。那么B+树是怎么演化来的呢?

    如果我们对二分查找树进行改造,树中的节点并不存储数据本身,而是只是作为索引。除此之外,我们把每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。

    image-20201105202855245

    如果我们要为上亿的数据建立索引,比如,我们给一亿个数据构建二叉查找树索引,那索引中会包含大约 1 亿个节点,每个节 点假设占用 16 个字节,那就需要大约 1GB 的内存空间。给一张表建立索引,我们需要 1GB 的内存空间。如果我们要给 10 张表建立索引,那对内存的需求是无法满足的。如何解决这个索引占用太多内存的问题呢?

    答案是把索引存放到硬盘中,但是如此一来,需要从硬盘中读取数据,速度是无法忍受的,因为树高是需要IO的次数,所以要降低树高。答案是多叉树。

    image-20201105211756394

    那多叉树的m到底是多少比较好呢?不管是内存中的数据,还是磁盘中的数据,操作系统都是按页(一页大小通常是 4KB,这 个值可以通过 getconfig PAGE_SIZE 命令查看)来读取的,一次会读一页的数据。如果要 读取的数据量超过一页的大小,就会触发多次 IO 操作。所以,我们在选择 m 大小的时 候,要尽量让每个节点的大小等于一个页的大小。读取一个节点,只需要一次磁盘 IO 操作。

    image-20201105212230406

    总结

    我们可以看到,任何一种技术都是为了解决在某些场景下,其他的方案存在的某些问题,我们要理清这种内部的关系,这样我们才可以了然于胸。

    这篇文章的字数不多,但是希望可以帮助你梳理下常见的数据结构,在学习的时候不至于迷茫。

    参考

    数据结构与算法之美

    本文由博客群发一文多发等运营工具平台 OpenWrite 发布

    展开全文
  • 大话数据结构

    2018-12-14 16:02:18
    项目经理看完代码后拍着桌子对他说:“你数据结构是怎么学的?” 1.3数据结构起源 4 1.4基本概念和术语 5 正所谓“巧妇难为无米之炊”,再强大的计算机,也要有“米”下锅才可以干活,否则就是一堆破铜烂铁。这个...
  • if((fp=fopen("3.txt","w+"))==NULL) //先新建一个3.txt,然后1.txt和2.txt的内容输入到里面 { printf("合并成绩文档失败,原因:建立文档出错"); exit(0); } student *p1=file1,*p2=file2; fprintf(fp,"%s...
  • 彻底理解堆

    2019-08-15 21:36:36
    在计算机科学中堆是一种很有趣的数据结构,实际上通常用数组来存储堆中的元素,但是我们却可以把数组中元素视为树,如图所示: 这就是一个普通的数组,但是我们可以其看做如下图所示的树: 这是怎么做到的呢? ...

    什么是堆

    在计算机科学中堆是一种很有趣的数据结构,实际上通常用数组来存储堆中的元素,但是我们却可以把数组中元素视为树,如图所示:

    在这里插入图片描述

    这就是一个普通的数组,但是我们可以将其看做如下图所示的树:

    在这里插入图片描述

    这是怎么做到的呢?

    原来虽然我们是在数组中存储的堆元素,但是这里面有一条隐藏的规律,如果你仔细看上图就会发现:

    • 每一个左子树节点的下标是父节点的2倍
    • 每一个右子树节点的下标是父节点的2倍再加1

    也就是说在数组中实际上隐藏了上面的这两条规律,如图所示:

    在这里插入图片描述

    堆这种数据结构最棒的地方在于我们无需像树一样存储左右子树的信息,而只需要通过下标运算就可以轻松的找到一个节点的左子树节点、右子树节点以及父节点,如下所示,相对于树这种数据结构来说堆更加节省内存。

    int parent(int i){   // 计算给定下标的父节点 
        return i/2;
    }
    int left(int i){     // 计算给定下标的左子树节点
        return 2*i;
    }
    int right(int i){    // 计算给定下标的右子树节点 
        return 2*i+1;
    }
    

    除了上述数组下标上的规律以外,你还会发现堆中的每一个节点的值都比左右子树节点大,这被称为大根堆,即对于大根堆来说以下一定成立:

    array[i] > array[left(i)] && array[i] > array[right(i)] == true
    

    相应的如果堆中每个一节点的值都比左右子树节点的值小,那么这被称为小根堆,即对于小根堆来说以下一定成立:

    array[i] < array[left(i)] && array[i] < array[right(i)] == true
    

    以上就是堆这种数据结构的全部内容了。

    那么接下来的问题就是,给定一个数组,我们该如何将数组中的值调整成一个堆呢?

     

    如何在给定数组上创建堆

    在这里我们以大根堆为例来讲解如何在给定数组上创建一个堆。

    给定数组的初始状态如下图a所示,从图中我们看到除array[2]之外其它所有节点都满足大根堆的要求了,接下来我们要做的就是把array[2]也调整成为大根堆,那么该怎么调整呢?

    在这里插入图片描述

    很简单,我们只需要将array[2]和其左右子树节点进行比较,最大的那个和array[2]进行交换,如图b所示,array[2]和其左子树array[4]以及右子树array[5]中最大的是array[4],这样array[2]和array[4]进行交换,这样array[2]就满足大根堆的要求了,如图b所示;

    但此时array[4]不满足要求,怎么办呢?还是重复上面的过程,在array[4]的左子树和右子树中选出一个最大的和array[4]交换,最终我们来到了图c,此时所有元素都满足了堆的要求,这个过程就好比石子在水中下沉,一些资料中将这个过程称形象的称为“shift down”。

    现在我们知道了假设堆中有一个元素i不满足大根堆的要求,那么该如何调整呢:

    void keep_max_heap(int i){
        int l = left(i);
        int r = right(i);
        int larget = i;
        if (l < heap_size && array[l] > array[i])
            larget = l;
        if (r < heap_size && array[r] > array[larget])
            larget = r;
        if (larget != i){
            swap(array[larget], array[i]);
            max_heap(larget);
        }
    }
    

    以上代码即keep_max_heap函数就是刚才讲解调整节点的过程,该过程的时间复杂度为O(logn)。

    但是到目前为止我们依然不知道该如何在给定的数组上创建堆,不要着急,我们首先来观察一下给定的数组的初始状态,如图所示:

    在这里插入图片描述

    实际上堆是一颗完全二叉树,那么这对于我们来说有什么用呢?这个性质非常有用,这个性质告诉我们要想将一个数组转换为堆,我们只需要从第一个非叶子节点开始调整即可

    那么第一个非叶子节点在哪里呢?假设堆的大小为heap_size,那么第一个非叶子节点就是:

    heap_size / 2;
    

    可这是为什么呢?原因很简单,因为第一个非叶子节点总是最后一个节点的父节点,因此第一个非叶子节点就是:

    parent(heap_size) == heap_size / 2
    

    有了这些准备知识就可以将数组转为堆了,我们只需要依次在第一个非叶子节点到第二个节点上调用一下keep_max_heap就可以了:

    void build_max_heap() {
        for (int i = heap_size/2; i>=1; i--)
            keep_max_heap(i);
    }
    

    这样,一个堆就建成了。

     

    增加堆节点以及删除堆节点

    对于堆这种数据结构来说除了在给定数组上创建出一个堆之外,还需要支持增加节点以及删除节点的操作,在这里我们依然以大根堆为例来讲解,首先来看删除堆节点。

    删除节点

    删除堆中的一个节点实际用到的正是keep_max_heap这个过程,假设删除的是节点i,那么我只需要将节点i和最后一个元素交换,并且在节点i上调用keep_max_heep函数就可以了:

    void delete_heep_node(int i) {
        swap(array[i], array[heap_size]);
        --heap_size;
        keep_max_heap(i);
    }
    

    注意在该过程中不要忘了将堆的大小减一。

    增加节点

    增加堆中的一个节点相对容易,如图所示,假设堆中新增了一个节点16,那么该如何位置堆的性质呢?很简单,我们只需要将16和其父节点进行比较,如果不符合要求就交换,并重复该过程直到根节点为止,这个过程就好比水中的气泡上浮,有的资料也将这个过程形象的称为“shift up”,该过程的时间复杂度为O(logn)。

    在这里插入图片描述
    用代码表示就是如下add_heap_node函数:

    void add_heap_node(int i){
        if (i == 0)
            return;
        int p = parent(i);
        if(array[i] > array[p]) {
            swap(array[i], array[p]);
            add_heap_node(p);
        }
    }
    

    至此,关于堆的性质、堆的创建以及增删就讲解完毕了,接下来我们看一下堆这种数据结构都可以用来做些什么。

     

    堆的应用

    在这一节中我们介绍三种堆常见的应用场景。
     

    排序

    有的同学可能会有疑问,堆这种数据结构该如何来排序呢?

    让我们来仔细想一想,对于大根堆来说其性质就是所有节点的值都比其左子树节点和右子树节点的值要大,那么我们很容易得出以下结论,对于大根堆来说:

    堆中的第一个元素就是所有元素的最大值。

    有了这样一个结论就可以将堆应用在排序上了:

    1. 将大根堆中的第一个元素和最后一个元素交换
    2. 堆大小减一
    3. 在第一个元素上调用keep_max_heap维持大根堆的性质

    这个过程能进行排序是很显然的,实际上我们就是不断的将数组中的最大值放到数组最后一个位置,次大值放到最大值的前一个位置,利用的就是大根堆的第一个元素是数组中所有元素最大值这个性质。

    用代码表示就如下所示:

    void heap_sort(){
        build_max_heap();
        for(int i=heap_size-1;i>=1;i--){
            swap(array[0],array[i]);
            --heap_size;
            keep_max_heap(0);
        }
    }
    

    执行完heap_sort函数后array中的值就是有序的了,堆排序的时间复杂度为O(nlogn)。

     

    求最大(最小)的K个数

    对于给定数组如何求出数组中最大的或者最小的K个数,有的同学可能觉得非常简单,不就是排个序然后就得到最大的或最小的K个数了吗,我们知道,排序的时间复杂度为O(nlogn),那么有没有什么更快的方法吗?

    答案是肯定的,堆可以来解决这个问题,在这里我们以求数组中最小的K个值为例。

    对于给定的数组,我们可以将数组中的前k个数建成一个大根堆,注意是大根堆,建成大根堆后array[0]就是这k个数中的最大值;

    接下来我们依次将数组中K+1之后的元素依次和array[0]进行比较:

    1. 如果比array[0]大,那么我们知道该元素一定不属于最小的K个数;
    2. 如果比array[0]小,那么我们知道array[0]就肯定不属于最小的K个数了,这时我们需要将该元素和array[0]进行交换,并在位置0上调用keep_max_heap函数维护大根堆的性质

    这样比较完后堆中的所有元素就是数组中最小的k个数,整个过程如下所示:

    void get_maxk(int k) {
        heap_size = k;     // 设置堆大小为k
        build_max_heap();  // 创建大小为k的堆
        for(int i=k;i<array.size();i++){
            if(array[i] >= array[0])  // 和堆顶元素进行比较,小于堆顶则处理
                continue;
            array[0] = array[i];
            keep_max_heap(0);
        }
    }
    

    那么对于求数组中最大的k个数呢,显然我们应该建立小根堆并进行同样的处理。

    注意使用堆来求解数组中最小K个元素的时间复杂度为O(nlogk),显然k<n,那么我们的算法优于排序算法。

     

    定时器是如何实现的

    我们要讲解的堆的最后一个应用是定时器,timer。

    定时器相信有的同学已经使用过了,定义一个timer变量,传入等待时间以及一个回调函数,到时后自动调用我们传入的回调函数,是不是很神奇,那么你有没有好奇过定时器到底是怎么实现的呢?

    我们先来看看定时器中都有什么,定时器中有两种东西:

    • 一个是我们传入的时延,比如传入2那就是等待2秒钟;传入10就是等待10秒钟;
    • 另一个是我们传入的回调函数,当定时器到时之后调用回调函数。

    因此我们要做的就是在用户指定的时间之后调用回调函数,就这么简单;为做到这一点,显然我们必须知道什么时间该调用用户传入的回调函数。

    最简单的一种实现方式是链表,我们将用户定义的定时器用链表管理起来,并按照等待时间大小降序链接,这样我们不断检查链表中第一个定时器的时间,如果到时后则调用其回调函数并将其从链表中删除。

    链表的这种实现方式比较简单,但是有一个缺点,那就是我们必须保持链表的有序性,在这种情况下向链表中新增一个定时器就需要遍历一边链表,因此时间复杂度为O(n),如果定时器很多就会有性能问题。

    那么该怎样改进呢?

    初看起来,堆这种数据结构和定时器八竿子打不着,但是如果你仔细想一想定时器的链表实现就会看到,我们实际上要找的仅仅就是时延最短的那一个定时器,链表之所以有序就是为此目的服务的,那么要得到一个数组中的最小值我们一定要让数组有序才可以吗?

    实际上我们无需维护数组的有序就可以轻松得到数组的最小值,答案就是刚学过的小根堆。

    只要我们将所有的定时器维护成为一个小根堆,那么我们就可以很简单的获取时延最小的那个定时器(堆顶),同时向堆中新增定时器无需遍历整个数组,其时间复杂度为O(logn),比起链表的实现要好很多。

    首先我们看一下定时器的定义:

    typedef void (*func)(void* d);
    class timer
    {
    public:
        timer(int delay, void* d, func cb) {
            expire = time(NULL) + delay;  // 计算定时器触发时间
            data = d;
            f = cb;
        }
        ~timer(){}
    
        time_t expire; // 定时器触发时间
        void* data;    // timer中所带的数据 
        func f;        // 操作数据的回调函数
        int i;         // 在堆中的位置
    };
    

    该定时器的定义非常简单,用户需要传入时延,回调函数以及回调函数的参数,注意在定时器内部我们记录的不是时延,而是将时延和当前的时间进行加和从而得到了触发定时器的时间,这样在处理定时器时只需要简单的比较当前时间和定时器触发时间的大小就可以了,同时使用i来记录该timer在堆中的位置。

    至于堆我们简单的使用vector而不是普通的数组,这样数组的扩增问题就要交给STL了 ?

    注意在这里定时器是从无到有一个一个加入到堆的,因此在向堆中加入定时器时就开始维护堆的性质,如下即为向堆中增加定时器add_timer函数:

    void add_timer(timer* t){
        if (heap_size == timers.size()){
            timers.push_back(t);
        } else {
            timers[heap_size]=t;
        }
        t->i = heap_size;
        ++heap_size;
        add_heap_node(heap_size-1);
    }
    

    当我们删除定时器节点时同样简单,就是堆的节点删除操作:

    void del_timer(timer* t){
        if (t == NULL || heap_size == 0)
            return;
        int pos = t->i;
        swap_pos(timers[pos], timers[heap_size-1]); // 注意不要忘了交换定时器下标
        swap(timers[pos], timers[heap_size-1]);
        --heap_size;
        keep_min_heap(pos); // 该函数实现请参见大根堆的keep_max_heap
    }
    

    当我可以向堆中增加删除定时器节点后就可以开始不断检测堆中是否有定时器超时了:

    void run(){
        while(heap_size) {
            if (time(NULL) < timers[0]->expire)  // 注意这里会导致CPU占用过高
                continue;                        // 真正使用时应该调用相应函数挂起等待
            if (timers[0]->f)
                timers[0]->f(timers[0]->data);   // 调用用户回调函数
            del_timer(timers[0]);
        }
    }
    

    注意在这种简单的实现方式下,当堆中没有定时器超时时会存在while循环的空转问题从而导致CPU使用率上升,在真正使用时应该调用相关的函数挂起等待。

     

    总结

    堆是一种性质非常优良的数据结构,在计算机科学中有着非常广泛的应用,希望大家能通过这篇文章掌握这种数据结构。

    如果你喜欢这篇文章,欢迎关注微信公共账号:码农的荒岛求生,获取更多精彩内容。

    在这里插入图片描述

    计算机基础决定程序员职业生涯高度

    展开全文
  • //为什么del函数里面还定义了一个指针q } if(NULL == p) { printf("********没有这个货物*********\n"); } else { printf(" 名称:%s\n 价格:%.2lf 数量:%d 所属类型:%s", p->name,p->p_price,p->p_num,p-...
  • //信息写入文件 //按任意键继续后清屏 system("pause"); system("cls"); } void user::save()//写文件登记的用户信息 { ofstream ofs; ofs.open(NAME, ios::out); for (int i = 0; i &...
  • 1574. 删除最短的子数组使剩余数组有序 1631. 最小体力消耗路径 1658. x 减到 0 的最小操作数 1697. 检查边长度限制的路径是否存在 1737. 满足三条件之一需改变的最少字符数 1834. 单线程 CPU 1899. ...
  • 2.2.9 A,B,C,D四个进程,A向buf里面数据,B,C,D向buf里面数据,当A写完,且B,C,D都读一次后,A才能再写。用P,V操作实现。 2.3.0 单向链表reverse,如ABCD变成DCBA,只能搜索链表一次。 2.3.1 二叉树的...
  • 还有数据的处理函数,数据发送函数,判断连接已断开的代码与第二节也是一模一样的,不过在这里我们需要额外的添加一段代码,当判断出连接已断开的时候,我们要客户端告知第一个程序结构进行删除客户端操作,...
  • 还有数据的处理函数,数据发送函数,判断连接已断开的代码与第二节也是一模一样的,不过在这里我们需要额外的添加一段代码,当判断出连接已断开的时候,我们要客户端告知第一个程序结构进行删除客户端操作,...
  • ) 如果某个主题或回复的作者已被删除,那么它的发表ID被置为999999,至于怎么处理就随便了,该作者的所有帖子不会显示. 短消息转换原则:以用户数据库(DV_User)的用户名为依据,数据库内没有...
  • 43、删除重复数据只保留一条。 55 44、一个几千万数据,发现数据查询很慢,怎么办? 55 六、Java高级部分 56 1、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用...
  • 165.一张自增表里面总共有 7 条数据删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几? 166.如何获取当前数据库版本? 167.说一下 ACID 是什么? 168.char 和 varchar 的区别是什么? 169...
  • C#基类库(苏飞版)

    2014-05-16 23:11:45
    主要功能:整GridView的数据导出到Excel中关增加一个效果线做美化 14.分词辅助类 SegList C#SegList分词辅助类,帮助类 15.汉字转拼音 EcanConvertToCh C#汉字转成拼音 PinYin 取汉字拼音的首字母,只要你输入...
  • 然后再p结点的指针指向p指针的的指针(即下一结点),p结点(即第一结点)的数据输出。重复执行此步聚直到p指针指向NULL为止。 N-S流程图如下: p=head,使指向第一个结点 输出p所指向的结点 p指向一下个...
  • 4)如果不清楚需要调用的数据的变量名,请用($item)}>打印出KT输出的变量数组,查看需要调用数据的变量名,用法如下: 推荐位名称" city_id=$request.city_id limit="推荐数量"}> ($item)}> 5)判断循环执行次数的...
  • java 面试题 总结

    2009-09-16 08:45:34
    ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector...
  • 世界500强面试题.pdf

    2019-11-01 14:33:26
    1.3.4. 输入一颗二元查找树,该树转换为它的镜像.................................... 48 1.3.5. 输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往 右的顺序打印................................
  • 里面主要包含跟 dom 相关的数据,我们无法直接合并到全局数据对象里,我们只感兴趣的部分传入 action 函数而已。 所以,是 event 响应函数里主动调用了 action 函数,并且传入它需要的...
  • 最新Java面试宝典pdf版

    热门讨论 2011-08-31 11:29:22
    11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript&ajax部分 82 1. 判断第二个日期比第一...
  • Java面试宝典-经典

    2015-03-28 21:44:36
    11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 81 三. html&JavaScript&ajax部分 82 1. 判断第二个日期比第一...
  • Java面试宝典2010版

    2011-06-27 09:48:27
    11、有数组a[n],用java代码将数组元素顺序颠倒 80 12.金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 三. html&JavaScript&ajax部分 1. 判断第二个日期比第一个...

空空如也

空空如也

1 2 3
收藏数 46
精华内容 18
关键字:

怎么将数组里面数据删除