数据结构(一):数组与链表

最近打算重新从基础开始学习,本文是学习过程中的笔记,如有错误请指正~

一、理论知识

数组与链表都是数据结构中最简单的线性结构,是数据存储的物理结构,很多别的数据结构都是从数组和链表衍生出来的,比如栈、队列、哈希表等

1. 数组(Array)

定义

有限相同类型的变量组成的有序集合

存储

  • 有限:数组的长度在创建时就已经确定了,所以如果操作不当很容易出现数组越界问题
  • 有序:数组在内存中是顺序存储的,并且元素之间是紧密排列的,中间不会出现空余的内存块,所以数组的插入、删除都需要靠移动元素来腾出空间给新元素安家。

读取

  • 数组中的每个元素都有下标(从0开始),可以按照元素的下标读取元素(随机读取),读取的效率比较高

从上面可以看出来数组的优势在于能够快速定位元素,更适合读多写少的场景

2. 链表(Linked List)

定义

物理上非连续、非顺序的数据结构,由若干节点(node)所组成

  • 每个node包括存放数据的变量data和指向下一个节点的指针next
  • 第一个节点称为头节点,最后一个节点称为尾节点

存储

  • 随机存储:链表的每一个节点分布在内存的不同位置,依靠next指针关联。可以很灵活有效地利用零散的碎片空间
  • 理论上无限:只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要考虑扩容问题

读取

  • 因为存储的随机性,所以只能根据节点next指针一个个找,从头节点开始向后一个一个节点逐一查找

从上面可以看出链表的优势在于能够灵活地进行插入和删除操作,如果需要在尾部频繁插入、删除元素,用链表更适合

二、源码

  • 数组长度是有限的,所以插入新元素时需要判断长度是否足够长
  • 链表的插入、删除其实就是刷next指针

数组

/**
 * 数组
 * @author yangjunqiang
 *
 */
public class MyArrayDemo {
    
    private int[] array;
    private int size;
    
    public static void main(String[] args) {
        MyArrayDemo myArrayDemo = new MyArrayDemo(10);
        myArrayDemo.insert(1, 0);
        myArrayDemo.insert(3, 1);
        myArrayDemo.insert(5, 2);
        myArrayDemo.insert(7, 3);
        myArrayDemo.insert(9, 1);
        myArrayDemo.printf();
    }
    
    // 初始化数组,并指定大小
    public MyArrayDemo(int capacity) {
        this.array = new int[capacity];
        size = 0;
    }
    
    public void insert(int element, int index) {
        if(index < 0 || index > size) {
            throw new IndexOutOfBoundsException("超出数组长度");
        }
        
        if(size >= array.length) {
            resize();
        }
        
        // 从右到左,将元素后移一位
        for(int i = size - 1; i > index; i--) {
            System.out.println("i = " + i + " = " + array[i]);
            array[i+1] = array[i];
        }
        
        array[index] = element;
        size++;
    }
    
    /**
     * 数组扩容
     */
    public void resize() {
        // 每次扩容,都是原数组大小的两倍
        int[] newArray = new int[array.length * 2];
        // 扩容的原理就是将旧数组的元素copy到新的数组里
        System.arraycopy(array, 0, newArray, 0, array.length);
        array = newArray;
    }
    
    public void update(int element, int index) {
        if(index < 0 || index > size) {
            throw new IndexOutOfBoundsException("超出数组长度");
        }
        array[index] = element;
    }
    
    
    /**
     * 移除元素
     * @param index
     */
    public int remove(int index) {
        // 下标从0开始,所以index最大是size-1
        if(index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("超出数组长度");
        }
        
        int deletedElement = array[index];
        
        // 从左到右,挨个将元素前移一位
        for(int i = index; i < size -1; i++) {
            array[i] = array[i+1];
        }
        size--;
        return deletedElement;
    }
    
    public void printf() {
        for(int i = 0; i < size; i++) {
            System.out.println(array[i]);
        }
    }

}

链表

/**
 * 链表
 * 链表元素的定位全靠next指针
 * 链表的优势在于能够灵活地插入、删除,但是查找效率低于数组
 * 如果内存是无限的,那么链表也将是无限的
 * @author yangjunqiang
 *
 */
public class MyLinkedList {

 // 头节点
    private Node head;
    // 尾节点
    private Node last;
    private int size;
    
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.insert(1, 0);
        myLinkedList.insert(3, 1);
        myLinkedList.insert(5, 2);
        myLinkedList.insert(7, 3);
        myLinkedList.insert(9, 4);
        myLinkedList.output();
    }
    
    /**
     * 查找元素
     * @param index 位置
     * @return
     */
    public Node get(int index) {
        if(index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("超出链表节点范围");
        }
        Node temp = head;
        
        // 链表的查找,只能挨个查找,因为链表的每个节点只保存了数据和next指针
        for(int i = 0; i < index; i++) {
            temp = temp.next;
        }
        return temp;
        
    }
    
    /**
     * 插入元素
     * @param data 数组
     * @param index 要插入到的位置
     */
    public void insert(int data, int index) {

        if(index < 0 || index > size) {
            throw new IndexOutOfBoundsException("超出链表节点范围");
        }
        // 待插入的节点
        Node insertNode = new Node(data);

        // 空链表,插入后也只有一个元素
        if(size == 0) {
            head = insertNode;
            last = insertNode;
        }
        // 在头部插入,将新插入的元素next指针指向原来的head节点,把新节点变为链表的head节点
        else if(index == 0) {
            insertNode.next = head;
            head = insertNode;
        }
        // 在尾部插入,将原来的last的next指针指向新插入的元素,并将新元素置为last
        else if(size == index) {
            last.next = insertNode;
            last = insertNode;
        }
        // 在中间插入
        else {
            // 获取前一个节点的信息
            Node prevNode = get(index - 1);
            // 插入值相当于替换了prevNode,所以next指针就是prevNode的next指针
            insertNode.next = prevNode.next;
            // prevNode的next指针改为insertNode
            prevNode.next = insertNode;
        }
        size++;
    }
    
    /**
     * 移除节点
     * @param index 
     * @return
     */
    public Node remove(int index) {
        if(index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("超出链表节点范围");
        }
        
        Node removeNode = null;
        // 从头删除,将head的值置为它的下一个值就好
        if(index == 0) {
            removeNode = head;
            head = head.next;
        }
        // 删除最后的元素
        else if(index == size - 1) {
            Node prevNode = get(index - 1);
            removeNode = prevNode.next;
            prevNode.next = null;
            last = prevNode;
        }
        // 删除中间的元素
        else {
            // 将待删除元素的前一个元素的next指针改为待删除元素的next指针
            Node prevNode = get(index - 1);
            Node nextNode = prevNode.next.next;
            removeNode = prevNode.next;
            prevNode.next = nextNode;
        }
        size--;
        return removeNode;
    }
    
    public void output() {
        Node temp = head;
        while( temp != null) {
            System.out.println(temp.data);
            temp = temp.next;
        }
    }
    
}

定义Node

public class Node {

    // 存储的数据
    int data;
    // 下一个节点
    Node next;

    public Node(int data) {
        this.data = data;
    }
}

三、参考

  • 《漫画算法-小灰的算法之旅》

附件

思维导图-数组与链表
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,362评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,577评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,486评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,852评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,600评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,944评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,944评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,108评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,652评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,385评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,616评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,111评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,798评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,205评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,537评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,334评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,570评论 2 379

推荐阅读更多精彩内容