数据结构与算法

  1. 时间复杂度

    常数操作包括加减乘除,以及从数组中取出一个值(因为直接计算偏移量,是一块连续的区域)

    注意:从list中取出一个值不是常数操作,因为需要遍历去找

    时间复杂度就是计算存在多少个常数操作

    且忽略低阶项,只要高阶项,且忽略高阶项的系数

  2. 通过亦或完成交换算法

    def swap():
        a = a ^ b
        b = a ^ b
        a = a ^ b
    

    为什么呢?

    假设 a = 甲 b = 乙

    a = a ^ b a = 甲 ^ 乙

    b = a ^b b = 甲乙 =甲 (两个相同的数亦或 = 0)(任何一个数亦或0都为它自己)

    a = a ^b a = 甲 ^ 乙^甲 = 乙

    前提是:a和b内存中的位置不可以相等(数组中相同位置)

    不建议使用

  3. 位运算取出最后一个1

    a & (~a + 1)

    一个数与上他的取反加一,就提取除了最后一位为1的数

  4. 排序

    • 冒泡排序:相邻位置的数进行比较,大的放到后面,比如,位置1的数与位置2的数比较,位置2的数与位置3的数比较,一轮下来找到最大的数,也就是最右的数,然后开始1-n-2的下一轮比较
    • 选择排序:遍历i到n-1找到最小值放入i位置上,i依次++
    • 插入排序:拿到一个数,位置为n,与前一个数(n-1)比较,比(n-1)位置上的数大则不交换,因为0-(n-1)一定有序,比(n-1)位置上的数小则交换,直至前面的数大于自己,或前方没有数
  5. 二分

    • 有序数组,是否存在某个数num
    • 有序数组,找到大于等于某个数num的最左位置:二分法,这个数的左侧一定是小于num的,右侧一定是等于或大于num的,以此为条件二分
    • 无序数组,找到其局部最小:局部最小一定是左侧是向下趋势的,即左侧的数比右侧的大;右侧也是向下趋势的,即右侧的数比左侧的大。先看两边,如果满足条件,再二分
  6. 归并排序

    二分,让左侧的数组与右侧的数组分别有序,再merge左侧数组与右侧数组

    merge的逻辑是:开辟一块新的空间存放排好序的help数组。左侧数组的i(i=0;i++)位置的数与右侧数组j(j=0;j++)位置的数比较,谁小就添加到下方help数组中,再i++或j++,再进行比较,直到一方越界,再将剩下的数全部拷贝到help中来。

    时间复杂度:O(NlogN)

    时间复杂度之所以优化了,是因为没有浪费排序的结果。比较选择排序而言,遍历一次只找到一个最小(或最大)的数,没有充分利用其的比较。

  7. 归并变形:求小和

    求一个数组中,比i小且位置在i左侧的数的和。遍历数组中所有的数的加和,即为小和

    也可转换需求,即求右侧比自己大的数的个数,然后乘自己。遍历数组中所有的数再加和,也为小和。

    找到右侧比自己大的数的个数,利用归并排序中的merge方法,如果左侧的数比右侧的数小,则小和sum+左侧的数*右侧排好序的个数,右侧排好序的个数通过下标很容易计算出来,然后将左侧的数放入help数组中。如果左侧的数与右侧的数相等,将右侧的数先copy到help数组中。这样才能知道有多少数比左侧的数大。如果左侧的数大,则将右侧数copy到help中,指针右移。

  8. 荷兰国旗问题

    • 问题一:给定一个num,使小于它的数放在左边,大于它的数放到右边,不要求有序。时间复杂度O(n),空间复杂度O(1)

      def dutch_flag_reduce(_arr, _num):
          index = 0
          jx = -1
          while index < len(_arr):
              #[index]<=num时,[index]与jx的下一个数交换,jx+1,index继续遍历
              if _arr[index] <= _num:
                  swap(_arr, jx + 1, index)
                  jx += 1
              #[index]>num时,index继续遍历
              index += 1
      
    • 问题二:在问题一的基础上,使小于它的数放在左边,大于它的数放到右边,等于num的数放在中间。

      def dutch_flag(_arr, _num):
          index = 0
          left = -1
          right = len(_arr)
          while index < right:
              #[index]<num时,[index]与left的下一个数交换,left+1,index继续遍历
              if _arr[index] < _num:
                  swap(_arr, left + 1, index)
                  left += 1
                  index += 1
              #[index]=num时,index继续遍历
              elif _arr[index] == _num:
                  index += 1
              #[index]>num时,[index]与right的上一个数交换,right-1,index不变。因为需要继续比较当前index的交换来的新数
              elif _arr[index] > _num:
                  swap(_arr, right - 1, index)
                  right -= 1 
      
  9. 快速排序

    • 方案一:以数组中最后一个数为num,对前面的数采用荷兰国旗问题一的解法,最后将num与大于区域的第一个交换。然后递归其左侧和右侧。

    • 方案二:采用荷兰国旗问题二的解法,再对大于区域和小于区域递归,等于区域不动。对比方案一,少了等于区域那部分的递归,稍微快一些。

    • 时间复杂度:如果每次num都正好在数组的中间时,符合master公式,时间复杂度O(nlogN)。

      而 如果每次num正好在一侧,相当于一侧的递归范围为0时,符合等差数列,时间复杂度(On^2)

  10. 堆中存在heapsize维护堆的范围

    是一个近似完全二叉树的二叉树,每个父节点都大于等于他的子节点,叫做大根堆

    当已知一个位置i,他的父节点为(i-1)/2,左子节点为2i+1,右子节点2i+2

    大根堆操作:

    • heapInsert:

      当用户给我数字时,如何维持大根堆。

      与父节点比较,若大于父节点,则交换,继续与当前父节点比较,若大于父节点,则交换,直至数组越界,或不大于了

    • heapify:

      去除当前大根堆最大节点,将数组最后一个数放到该父节点上,如何维护大根堆

      左孩子与右孩子比较,最大的一个与父节点比较,若大于父节点则与父节点交换。

      交换后该节点继续与其左孩子与右孩子比较

  11. 堆排序

    • 当那个heapsize为1时,将0、1位置的数heappInsert,heapsize++,接着heapInsert构建出大根堆结构

      将最后位置的数与0位置的数交换,然后进行heapify,heapsize--,直至heapsize = 0,排序成功

      时间复杂度 O(NlogN) 空间复杂度O(1)

    • 反正第一步也是维护大根堆结构,可以用heapify。从数组最后的节点做heapify,依次向前。O(NlogN)

  12. 堆扩展算法

    一个几乎有序的数组,几乎有序是指,如果把数组排好序的话,每个元素的移动距离不超过k,且k相对与数组来说比较小

    堆排序,对i与i+k之间的数维护成小根堆,放在i位置上,接着i++,对i+1与i+k+1位置上的数维护成小根堆,最后将剩余的小根堆中的数依次停好

  13. 堆结构

    每个编程语言中都有堆结构,其内部结构为数组,堆长度通过heapsize控制。当数组长度不够时,会2n扩容。

    编程语言中的堆结构不建议手写修改一个数,更改堆结构。需要更改堆结构时,手写堆。

    系统中的堆结构建议是你给它一个数,他给你一个数。

    java中只有小根堆,使用比较器改成大根堆

  14. 计数排序

    准备一个辅助数组,他的下标位置上的数代表当前排序数组中存在多少该下标的数,最后将其依次展开。

    适合有范围的数据状况,比如年龄排序,辅助数组的范围可以是(0,200)

  15. 基数排序

    排序一组十进制的数,准备10个桶,从个位数依次进桶,比如11进入1号桶,56进入6号桶,36进入6号桶,接着依次倒出,先进先出。接着排序十位,然后百位,倒出来后就已经排好序了。

    原理是先排个位数,然后排十位。因为位数越高优先级越高。

  16. 基数排序代码层面优化

    准备一个help数组与原数组长度一致,准备一个原数组位数长度的count数组。

    遍历原数组,将其最后一位的词频记录到count数组中,将count数组每一index上的数与前面的数累加

    例:原count数组为:0,1,1,4,6。转换为:0,1,2,6,12

    含义为原数组个位数<=当前index的数有几个

    接着从后遍历原数组,如果数字为22,则看count数组上3位置的数有4个,说明它前面存在3个<=它的数,所以它应该在第4个位置上(help数组),接着4--。继续向前遍历。当前数组遍历完成。相当于对个位数进行了一次入桶。将help数组转移到原数组中,相当于出桶。

    然后继续遍历十位上的数。

posted @ 2024-05-31 09:34  疯啦吧你  阅读(4)  评论(0编辑  收藏  举报