IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构(算法篇) -> 正文阅读

[数据结构与算法]数据结构(算法篇)

数据结构算法篇

算法的魅力

使用经典的百鸡百钱问题来说明,一共列举了三种方式,每种方式的循环层数都不一样

//三层循环
long start1 = System.nanoTime();
for (int i = 0; i < 20; i++) {
    for (int j = 0; j < 34; j++) {
        for (int k = 0; k < 300; k = k + 3) { //k = k + 3 是为了满足小鸡实际存在的要求
            if (5 * i + 3 * j + k / 3 == 100 && i + j + k == 100) {
                System.out.println(i + "-" + j + "-" + k);
            }
        }
    }
}
long end1 = System.nanoTime();
System.out.println("三层循环耗时:" + (end1 - start1));

//两层循环
long start2 = System.nanoTime();
int k_temp = 0, k = 0;
for (int i = 0; i < 20; i++) {
    for (int j = 0; j < 34; j++) {
        k_temp = 100 - i - j;
        if (k_temp % 3 == 0) {
            k = k_temp;
        }
        if (5 * i + 3 * j + k / 3 == 100 && i + j + k == 100) {
            System.out.println(i + "-" + j + "-" + k);
        }
    }
}
long end2 = System.nanoTime();
System.out.println("两层循环耗时:" + (end2 - start2));

/**
         * 一层循环  运用到了数学里面的代替法则,所以说程序最尖端的技术是算法,算法最基本的素养是数学
             * 第一步:x,y与z的关系
             * 5x + 3y + (100 - x - y)/3 = 100 --->y = 25 - 2x + x/4
             *
             * 第二步:替代变量t,演变为
             * t = x/4;
             * y = 25-2x+t
             *
             * 第三步:得到x,y,z与替代变量关系
             * y = 25-2*4t + t ==> y = 25-7t
             * x = 4t
             * z = 100 - 25 + 7t -4t = 75+3t;
             *
             * 第四步:得到t的最大值,要保证x,y,z都要大于等于0,所以 t为25/7 取整得3
             * x = 4t                           -->0 = 4*t --->t = 0 没任何意义
             * y = 25-7t                        -->0 = 25-7t --->t = 25/7
             * z = 100 - 25 + 7t -4t = 75+3t;   -->0 =75+3t --->t = -25 没意义
             *
             * 第五步:判断依据(5 * x + 3 * y + z / 3) == 100 && (x + y + z) == 100
         */
long start3 = System.nanoTime();
int x, y, z, t;
for (t = 0; t <= 3; t++) {
    x = 4 * t;
    y = 25 - 7 * t;
    z = 75 + 3 * t;
    if ((5 * x + 3 * y + z / 3) == 100 && (x + y + z) == 100) {
        System.out.println(x + "-" + y + "-" + z);
    }
}
long end3 = System.nanoTime();
System.out.println("一层循环耗时:" + (end3 - start3));

#输出结果
    0-25-75
    4-18-78
    8-11-81
    12-4-84
    三层循环耗时:23438800
    0-25-75
    4-18-78
    8-11-81
    12-4-84
    两层循环耗时:432300
    0-25-75
    4-18-78
    8-11-81
    12-4-84
    一层循环耗时:280700

结论:很明显,循环嵌套层越少,效率越高

算法概念

时间复杂度

时间复杂度是对一个算法在运行过程中计算耗时的一个量度,也是优化算法的最核心的目标依据。

空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,这个概念在很早以前是需要关注的,但是随着硬件设施的提升和廉价,空间复杂度逐渐变成了边角料,我们只需要在平常的程序设计中尽量注意一些习惯就好:

1、尽量用一维数组,二维数组用结构体一维数组代替
2、尽量少引入中间状态、临时状态,减少状态维护占用
3、数组使用尽量注意小初始化,高扩容频率(这个影响到了性能,慎用)
.....

常见的数据结构

  • **栈(Stack):**线性表。类似于弹匣,只能从一个入口压栈、弹栈—先进后出。
  • **队列(Queue):**线性表。只允许在表的一端进行插入操作,而在另一端进行删除操作—先进先出。
  • **数组(Array):**数组是一种聚合数据类型,它是将具有相同类型的若干变量有序地组织在一起的集合。
  • **链表(Linked List):**物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
  • **堆(Heap):**一种特殊的树形数据结构,一般讨论的堆都是二叉树。
  • **散列表(Hash table):**散列表源自于散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较操作而直接取得所查记录。

排序优化

准备工作

定义一个随机数生成方法:

/**
 * 生成随机数组
 *
 * @param len
 * @param max
 * @return
 */
public static Integer[] generateRandomArray(int len, int max) {
    Integer[] arr = new Integer[len];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = (int) (Math.random() * max);
    }
    return arr;
}

测试遍历方法:

public static void main(String[] args) {
    int N = 20000;
    Integer[] arr = generateRandomArray(N, 100000);
    long start = System.currentTimeMillis();
    sort(arr);
    long end = System.currentTimeMillis();
    System.out.println("**排序耗时:" + (end - start));
    System.out.println(Arrays.asList(arr));
}

元素位置交换方法:

	/**
     * 元素位置交换
     *
     * @param arr
     * @param i
     * @param j
     */
    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

冒泡排序

依次比较相邻的两个数,将小数放在前面,大数放在后面。

第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。

第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。

如此下去,重复以上过程,直至最终完成排序。

/**
 * 核心代码---双层循环比对
 *
 * @param arr
 */
public static void sort(Integer[] arr) {
    int count = 0;
    for (int i = 0; i < arr.length - 1; i++) {//冒泡趟数
        for (int j = 0; j < arr.length - 1 - i; j++) {
            count++;
            if (arr[j + 1] < arr[j]) {
                swap(arr, j + 1, j);
            }
        }
    }
    System.out.println("循环计算次数:" + count);
}

#输出结果(无论你执行多少次,循环次数都是固定的)
    循环计算次数:199990000
	冒泡排序耗时:1529

结论:两层循环,性能不太理想

冒泡排序优化

改进方法:如果我们发现在排序过程中,没有发生一次交换,我们可以让它提前结束!

我们在排序过程中设置一个标标记flag判断一下元素是否进行交换

	/**
     * 核心代码---双层循环比对
     *
     * @param arr
     */
    public static void sort(Integer[] arr) {
        int count = 0;
        //标记变量,表示是否进行过交换,如果没有,说明没必要继续往后面比较了
        boolean flag = false;
        for (int i = 0; i < arr.length - 1; i++) {//冒泡趟数
            for (int j = 0; j < arr.length - 1 - i; j++) {
                count++;
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    flag = true;
                }
                if (!flag) {
                    //没有发生交换则退出循环;
                    break;
                }
            }
        }
        System.out.println("循环计算次数:" + count);
    }

#输出结果(因为随机顺序的不确定性,多执行几次)
    1、循环计算次数:199990000
	   冒泡排序耗时:1654
    2、循环计算次数:19999
	   冒泡排序耗时:14

结论:如果生成的随机数组无序性非常大,执行的计算次数越多,最多时等于普通冒泡排序计算次数;如果是数组是有序的,则可以大大节省时间!

插入排序

插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较 N-1 次,时间复杂度为 O(N)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n^2)

	/**
     * 核心代码---双层循环比对
     *
     * @param arr
     */
    public static void sort(Integer[] arr) {
        int count = 0;
        for (int i = 0; i < arr.length; i++) {
            // 寻找元素 arr[i] 合适的插入位置
            for (int j = i; j > 0; j--){
                count++;
                if (arr[j].compareTo(arr[j - 1]) < 0){
                    swap(arr, j, j - 1);
                }else{
                    break;
                }
            }

        }
        System.out.println("循环计算次数:" + count);
    }

#输出结果
    循环计算次数:100196062
	插入排序耗时:591

结论:循环计算次数相较于普通冒泡排序少了将近一半,原因就是它对于已经排好序的部分不会再次循环,而是将一个记录插入到已经排好序的有序表中,相当于变成了一个记录数增 1 的有序表

希尔排序

希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。

/**
 * 核心代码---循环比对
 *
 * @param arr
 */
public static void sort(Integer[] arr) {
    int count = 0;
    int j;
    for (int gap = arr.length / 2; gap >  0; gap = gap / 2) {
        for (int i = gap; i < arr.length; i++) {
            Integer tmp = arr[i];
            for (j = i; j >= gap && tmp.compareTo(arr[j - gap]) < 0; j = j -gap) {
                count ++;
                arr[j] = arr[j - gap];
            }
            arr[j] = tmp;
        }
    }
    System.out.println("循环计算次数:" + count);
}

#输出结果
    循环计算次数:361088
	希尔排序耗时:32

结论:比以上任何排序都快,原因就在于通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 23:38:43  更:2022-04-01 23:41:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 9:44:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码