常见数据结构与算法

一 数据结构和算法

1.1 什么是数据结构

1.2 数据结构分类

分为逻辑结构和物理结构

逻辑结构分类:

a.集合结构(无关系)

b.线性结构(一对一)

c.树形结构(一对多)

d.图形结构(多对多)

物理结构分类:

顺序存储结构:

把数据元素存储到地址连续的存储单元,比如数组

优点:随机访问查找的效率高

缺点:插入和删除要移动一部分数组,效率低

链表存储结构:

把数据元素存放在任意的存储单元,地址可以连续可以不连续。此时引入了一个指针存放数据元素的地址 来反映逻辑关系,这样通过地址就可以找到相关联数据元素的位置。

优点

  • 插入删除速度快
  • 内存利用率高,不会浪费内存
  • 大小没有固定,拓展很灵活

缺点

  • 不能随机查找,必须从第一个开始遍历,查找效率低。

1.3 算法

概念: 用系统的方法解决问题的策略机制。

1.3.1 时间复杂度

int aFunc(void) {
    printf("Hello, World!\n");      //  需要执行 1 次
    return 0;       // 需要执行 1 次
}
int aFunc(int n) {
    for(int i = 0; i<n; i++) {         // for循环判断需要执行 (n + 1) 次
        printf("Hello, World!\n");      // 需要执行 n 次
    }
    return 0;       // 需要执行 1 次
}

我们把 算法需要执行的运算次数 用 输入大小n 的函数 表示,即 T(n) 。

此时为了 估算算法需要的运行时间 和 简化算法分析,我们引入时间复杂度的概念。

算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 输入大小n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。

分析一个算法的运行时间,需要分析核 心操作的次数和输入规模的关系在这里插入图片描述

n是输入规模,f(n) 是输入规模对应的执行次数

某一程序可能存在一下情况:

在这里插入图片描述

可以看出:

  1. 当n>2时,算法a1的渐进增长小于苏纳法b2的渐进增长
  2. 随着输入规模增大,算法的常数操作可以忽略不计

随着输入规模的增大,与最高次项相乘的常熟可以忽略

在这里插入图片描述

1.3.2大O记法

"大O表示法"表示程序的执行时间或占用空间随数据规模的增长趋势.

T(n)是关于问题规模n的函数.算法的时间复杂度,就是算法的时间量度,记作T(n)=O(f(n)). 他表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,f(n)是问题规模n的 某个函数.

执行次数 = 执行时间

n:数据规模,通俗点说就是函数中的那个变量n

f(n):代码总的执行次数和数据规模的关系

T(n):代码的执行 时间(并不是代码实际的执行时间,这里表示代码执行时间和数据规模之间的关系)

常见的大O阶

  1. 线性阶

O(n)

  1. 平方阶

O(n^2)

  1. 立方阶

O(n^3)

  1. 对数阶

O(logn)

5.常数阶

O(n)

他们的复杂程度从低到高

O(1)<O(logn)<O(n)<O(nlogn)<o(n2)<O(n3)

二 算法

常用算法

冒泡排序

{4,5,6,1,2,3}

{4,5,1,6,2,3}

{4,5,1,2,6,3}

{4,5,1,2,3,6}

{4,1,5,2,3,6}

{4,1,2,5,3,6}

{4,1,2,3,5,6}

{1,4,2,3,5,6}
在这里插入图片描述

选择排序

  1. 从头开始遍历 找到最小的数 与第一位数交换
  2. 从第二位开始遍历到最后,找到最小的数,与第二位数交换
  3. 以此类推
public static void sort(Comparable[] a) {
        int minIndex;
        //锁定一个 数
        for (int i = 0; i < a.length-1; i++) {
            //记录这个数的下标, 视为最小值的下标
            minIndex=i;
            //遍历这个数后面的数组
            for (int j = i+1; j < a.length ; j++) {
                //找到比这个数还要下的数 ,交换,
                if(greater(a[minIndex],a[j])){
                    //exch(a,minIndex,j);
                    minIndex=j;
                }
            }
            exch(a,minIndex,i);
        }

    }

    //true c1大, false c2大
    public static boolean greater(Comparable c1,Comparable c2){
        return c1.compareTo(c2) > 0;
    }

    public static void exch(Comparable a[],int i,int j){
        Comparable temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }

复杂度分析

比较次数:

(n-1)+(n-2)+…+2+1 =[(n-1)+1] * (n-1)/2 = =n^2/2-n/2

交换次数:

n-1

时间复杂度: n^2-n/2+n-1 = n^2+n/2-1

根据大O表示法,保留最高阶,去除常数因子,时间复杂度为O(n^2)

插入排序

实现思路

1.从数组的第二个数据开始往前比较,即一开始用第二个数和他前面的一个比较,如果 符合条件(比前面的大或者小,自定义),则让他们交换位置。

2.然后再用第三个数和第二个比较,符合则交换,但是此处还得继续往前比较,比如有 5个数8,15,20,45, 17,17比45小,需要交换,但是17也比20小,也要交换,当不需 要和15交换以后,说明也不需要和15前面的数据比较了,肯定不需要交换,因为前 面的数据都是有序的。

3.重复步骤二,一直到数据全都排完。

希尔排序

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

插入排序的优化版,极大提升了数据量大时的运行效率

public class Shell {
    /*
       对数组a中的元素进行排序
    */
    public static void sort(Comparable[] a){
        //1.根据数组a的长度,确定增长量h的初始值;
        int h = 1;
        while(h<a.length/2){
            h=2*h+1;
        }
        //2.希尔排序
        while(h>=1){
            //排序
            //2.1.找到待插入的元素
            for (int i=h;i<a.length;i++){
                //2.2把待插入的元素插入到有序数列中
                for (int j=i;j>=h;j-=h){

                    //待插入的元素是a[j],比较a[j]和a[j-h]
                    if (greater(a[j-h],a[j])){
                        //交换元素
                        exch(a,j-h,j);
                    }else{
                        //待插入元素已经找到了合适的位置,结束循环;
                        break;
                    }
                }

            }
            //减小h的值
            h= h/2;
        }

    }

    /*
        比较v元素是否大于w元素
     */
    private static  boolean greater(Comparable v,Comparable w){
        return v.compareTo(w)>0;
    }

    /*
    数组元素i和j交换位置
     */
    private static void exch(Comparable[] a,int i,int j){
        Comparable temp;
        temp = a[i];
        a[i]=a[j];
        a[j]=temp;
    }
}

归并排序

递归:

在递归中,不能无限制的调用自己,必须要又边界条件,因为每一次递归调用(方法的调用)都会在栈内存开辟新的空间,如果层级太深,容易造成栈内存溢出.

归并排序采用分治策略(分治法将问题(divide)成一些小的问题然后递归求解,而**治(conquer)**的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

快速排序

快速排序是对冒泡排序的一种改进

排序原理:

  1. 设定一个分界值,通过该分界值将数组分为左右2部分
  2. 小于分界值的数据放到数组左边,大的放右边.
  3. 左侧右侧继续独立排序,设定分界值,同样也分左右2部分.
  4. 重复上述过程,可以看出是个递归定义.通过递归将左侧排好序,在递归右侧.

最优情况,每次切分都是等分,复杂度 O(nlogn)

最坏情况: 每次切分的临界值都是序列的最大/最小值,复杂度O(n^2)

在这里插入图片描述

三 数据结构

线性表

头节点,后继元素…尾节点

除了头节点和尾节点,其他节点 都有一个前驱和后继

线性表的分类

存储方式可以是顺序存储也可以是链式存储,按照数据存储方式的不同,可以把线性表分为顺序表和链表.

顺序表

是以数组的形式保存的线性表,在java中以ArrayList为例

ArrayList

扩容机制

默认初始容量: private static final int DEFAULT_CAPACITY = 10;

//得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 获取默认的容量和传入参数的较大值
            //更新 要求最小容量
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

如果容量比10小,那么最小扩容量改为10,如果比10大,那么为大的值.

扩容条件: minCapacity - elementData.length > 0 也就是 现容量 小于 要求最小容量,该扩容

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//扩容方法
}

扩容方法

/**
 * 要分配的最大数组大小
 */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
    //下次扩容容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    } 

int newCapacity = oldCapacity + (oldCapacity >> 1);

扩容代码,相当于扩大1.5倍容量

链表

顺序表的虽然查询很快,时间复杂度为O(1), 但是,他的增删的效率比较低,因为每次增删都伴随大量的数据移动操作.

解决方案:使用链式存储结构.

LinkedList

(stack)只允许在有序的线性数据集合的一端(称为栈顶 top)进行加入数据(push)和移除数据(pop)。因而按照 后进先出(LIFO, Last In First Out) 的原理运作。在栈中,push 和 pop 的操作都发生在栈顶。

栈常用一维数组或链表来实现,用数组实现的栈叫作 顺序栈 ,用链表实现的栈叫作 链式栈

栈的常见应用常见应用场景
  • 实现浏览器的回退和前进功能
  • 检查符号是否成对出现
  • 反转字符串
  • 维护函数调用

队列

队列先进先出( FIFO,First In, First Out) 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 顺序队列 ,用链表实现的队列叫作 链式队列队列只允许在后端(rear)进行插入操作也就是 入队 enqueue,在前端(front)进行删除操作也就是出队 dequeue

https://www.cnblogs.com/xkzhangsanx/p/10888179.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值