数据结构笔记(一)

总览数据结构的内容

  • 线性表:零个或多个数据元素的有序序列
  • 队列:只允许在一端插入,而在另一端进行删除操作的线性表
  • 堆栈:栈是限定仅在表尾进行插入删除操作的线性表
  • 树:树是n个节点的有序集。节点可以像树一样越向叶子节点就没有交集
  • 图论:由顶点的有穷空集合和顶点之间边的集合组成
  • 排序和查找算法:排序是对数据进行顺序排列,查找是在大量数据中寻找我们需要的数据的过程

什么是数据结构?

  • 数据项:一个数据元素可以由若干数据项组成
  • 数据对象:有相同性质的数据元素的集合,是数据的子集
  • 数据结构:是相互之间存在一种或多种特定关系的数据元素集合

逻辑结构与物理结构##

逻辑结构:是指数据对象中数据元素之间的相互关系

  1. 集合结构
  2. 线性结构
  3. 树形结构
  4. 图形结构
image

image
image

image

物理结构:是指数据的逻辑结构在计算机中的存储

  1. 顺序存储结构
image
  1. 链式存储结构
image

数组

  1. 简单:数组是一种最简单的数据结构
  2. 占据连续内存:数据空间连续,按照申请的顺序存储,但是必须指定数组大小
  3. 数组空间效率低:数组中经常有空闲的区域没有得到充分的应用
  4. 操作麻烦:数组的增加和删除操作很麻烦

线性表

物理结构划分(一个萝卜一个坑,美女)

image

链式存储结构(天龙三兄弟)


image
  • 顺序表
image
image

a1是a2的前驱,ai+1是ai的后继,a1没有前驱,an没有后继
n为线性表长度,若n==0,线性表为空
数据节点:

class students{
    Student[40];
    int size;
    ...
}

下面从ArrayList的添加删除来看看顺序表:(从android studio中分离出来的方法)

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

 public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;

add方法中有一个参数和二个参数的方法,首先我们看一个参数的方法:
首先看看ensureCapacityInternal(size+1)从他的参数我们可以看到是整个ArrayList大小+1,如果ArrayList(elementData)数组等于初始的数组,就对比传入参数与默认的参数大小,两者取其大

 private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

如果较大的参数减去当前ArrayList的真实长度是大于0的,我们就走grow方法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

grow方法中有一个新的容积大小(newCapacity),他扩充的大小相当于当前ArrayList的size+size*2,这个值与minCapacity对比取其大值,并且限制不能小于int的MAX。做完判断就会走copeyOf方法

 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);
}

System.arraycopy(a,4,a1,5,6):把a数组的数据从下标 4 开始,移动到 a1数组下标是 5 ,移动6的长度

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

现在来看二个参数的add方法:
先走一个OutOfBoundsException的判断,如果当前要添加的位置index大于ArrayList的长度,或者小于0,根据前面的分析ensureCapacityInternal(size + 1)就是对ArrayList进行扩容,扩容完就继续复制,System.arraycopy(elementData, index, elementData, index + 1, size - index);
将elementData的下标为index复制到index+1处,复制的大小就是size-index,然后将要添加的值element插入到数组的index处,同时增加Arraylist的size,+1。
remove方法也有两种参数,一个是int下标,一个是object值:
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

先看传入int下标的方法,如果下标大于ArrayList长度,就出OutOfBoundsException。获取在该index上的元素,判断需要发生移动的数据量大小,然后将index+1的numMoved的数据移动到从index开始,同时将最后一位置为空。
如果传入了Object,就循环遍历出这个数据,并进行移除。
ArrayList类的关系如下:

image

ArrayList面试常见问题

ArrayList的大小是如何自动增加的?
ArrayList在add方法中会做出判断,如果当前长度过短,会增加size+size*2的长度

什么情况下你会使用ArrayList?
ArrayList在插入删除时候有明显的复杂度增加,因为他删除的时候是顺序表,插入删除都要移动size-index-1长度的数据。明白他的特性下,我会在保存数据同时对这段数据不进行排序删除等操作时候,我会优先使用ArrayList,如果要进行排序等操作我会选择LinkList。

在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么?
从ArrayList的remove方法我们可以看到,如果要删除某个对象,根据对象删除需要遍历整个数组,然后删除后进行位移,根据下标删除也需要进行位移,效率很低。但是在尾部删除或者添加,根据顺序表的规则,效率不低。

ArrayList如何顺序删除节点 ?
他是可以顺序删除节点的。通过迭代器顺序删除,each和for循环也可以删除,但是需要从后往前删除。如果从前往后删,根据顺序表的特点,0节点永远存在,删除了也会从1节点上面前移所以会报错。

arrayList的遍历方法
each和for循环,迭代器

线性表之链表

定义:线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的

class Node{
    Object data;     
    Node next;
}
image

单循环链表

image

双循环链表

image

链表的增删改查

image
//增
s.next=p.next
p.next=s
//删
p.next=p.next.next
//改
p.data=new data();
//查
while(p.next!=l){
    p=p.next;
}

顺序表 优点,存储空间连续允许随机访问尾插,尾删方便,缺点插入效率低,删除效率低,长度固定
单链表 优点,随意进行增删改,插入效率高,删除效率高O0,长度可以随意修改,缺点内存不连续,不能随机查找

双链表的存储结构单元
private static class Node<E>{
E item;
Node<E> next;
Node<E> prev;
}

image

双链表的表现形式

image

双链表的知识树

image
1:s.next=p.next;
2: p.next.prev=s;
3: s.prev=p;
4: p.next=s;
//步骤不能乱,否则就会出现跳跃式的循环,比如2,4调换,这样s=p.next,而p.next.prev=s,这样s这里就出现了一个死循环
image
p.next.prev=p.prev;
p.pre.next=p.next;
image

image

现在分析一下LinkedList的添加删除方法:首先是add()
public boolean add(E e) {
linkLast(e);
return true;
}
public void add(int index, E element) {
checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

单参数的add,方法很简单,只有一个likLast(e)
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

这是一个简单的尾插,我们可以从名称上看出来。首先获取最后一个节点l,同时new出来一个Node<>,从Node的构造方法,我们可以看出,第一个是新参数的prev指向的节点,而next为空。同时把尾值替换为新节点。然后是一个判断,当首节点为空,就把新参数作为首节点,否则就将首节点的next指向尾节点。
再看两个参数的add方法。当插入的位置为尾部,就使用尾插,否则就走linkBefore

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

拿出该插入位置的pred(p.prev),同时新建一个Node(s),将新节点的prev指向该prev(s.prev=pred),同时将新节点的next,指向原位置的节点(s.next=p)。判断如果新节点的prev为空,那就将新节点命名为首节点,否决就将老节点的next指向新节点(pred.next=s)

删除方法如下:

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

传入下标的方法相对简单
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

判断下标节点prev,若prev为空,这时候就在首节点位置,就把首节点复制为下标节点的next,否则就将下表节点的值赋为她的next(prev.next=next),同时把下标节点的prev指空。如果next位置为空,那么此时处于尾节点位置,将节点的next的prev指向prev,并将下标节点指向null。最后将节点赋空。
如果是传入Object,分两种情况进行遍历。

List总结

image
  1. List是一个接口,它继承于Collection的接口。它代表这有序的队列
  2. AbstractList是一个抽象类,它继承于AbstractCollection。AbstractList实现了List接口中除size()、get(int location)之外的函数
  3. AbstractSequentialList是一个抽象类,它继承于AbstractList。AbstractSequentialList实现了链表中,根据index索引值操作链表的全部函数
  4. ArrayList,LinkList,Vector,Stack是List的4个实现类
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容