赞
踩
数组是存放在连续内存空间上的相同类型数据的集合。可以方便的通过下标索引的方式获取到下标下对应的数据。
不能单独删除、释放数组中的某个元素,只能覆盖。如果要释放,就是全释放(程序运行结束,回收内存栈空间)。
因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。对于数组来说,在尾部插入、删除元素是比较高效的,时间复杂度是 O(1),但是如果在中间或者开头插入、删除元素,就会涉及数据的搬移,时间复杂度为 O(N),效率较低。
技巧:
把待删除元素交换到最后一个,然后再删除,就可以避免数据搬移。
使用双指针法,可原地修改数组
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链接的入口节点称为链表的头结点也就是head。
单链表中的节点只能指向下一个节点。
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表既可以向前查询也可以向后查询。
链表首尾相连
块状链表结合了数组和链表的特性,将连续成段的数组通过链接串起来。块状链表的特点是插入很灵活,寻找特定元素也比正常链表快速。
链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
这个链表起始节点为2, 终止节点为7, 各个节点分布在内存个不同地址空间上,通过指针串联在一起。
C/C++的定义链表节点方式:
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
Java定义链表节点:
public class ListNode { // 结点的值 int val; // 下一个结点 ListNode next; // 节点的构造函数(无参) public ListNode() { } // 节点的构造函数(有一个参数) public ListNode(int val) { this.val = val; } // 节点的构造函数(有两个参数) public ListNode(int val, ListNode next) { this.val = val; this.next = next; } }
Java单向链表定义:
public class LinkedList { static class ListNode( int val; ListNode next; public ListNode(int val) { this.val = val; } ListNode head; //头节点 ListNode tail;//尾节点 int size; public LinkedList() { //初始化 head = null; tail = null; size = 0; } }
只要将C节点的next指针 指向E节点就可以了。
C/C++里要手动释放D节点内存,有自动回收机制(Java、Python)则不用
public void delete(int number) { if(head != null && head.val == number) { //删除头节点 head = head.next; size--; if(size == 0) { //没有剩余元素 tail = head; } } else {//删除非头节点 ListNode prev = head; ListNode cur = head; while (prev != null && cur != null) { if (cur.val == number) { if(cur == tail) { //删除末尾元素 tail = prev; } prev.next = cur.next; size--; return; } prev = cur; cur = cur.next; } } }
链表的增添和删除都是 O ( 1 ) O(1) O(1)操作,也不会影响到其他节点。
但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是 O ( n ) O(n) O(n)。
在一个链表中插入新元素分为以下三种情况:
将新节点的next指针指向插入位置后的节点,再将插入节点前的next指针指向新插入的节点。
注意:
我们必须先执行步骤1,再执行步骤2;如果先执行步骤2,否则会导致插入位置后续的节点无法被找到。
public void insert(int position, int number) { if (position > size) { return; } ListNode newNode = new ListNode(number); if (position == 0) { newNode.next = head; head = newNode; if(tail == null) { tail = newNode; } size++; } else if (position == size) { this.append(number); } else { ListNode prev = head; for (int i = 0; i < position - 1; i++) { prev = prev.next; } ListNode next = prev.next; newNode.next = next; prev.next = newNode; size++; } } //末尾增添新元素 public void append(int number) { ListNode newNode = new ListNode(number); if(tail == null) { tail = newNode; } else { tail.next = newNode; tail = newNode; } size++; }
public int search(int number) {
ListNode cur = head;
for(int index = 0; cur != null; index++) {
if(cur.val == number) {
return index;
}
cur = cur.next;
}
return -1;
}
public int update(int oldValue, int newValue) {
ListNode cur = head;
for(int index = 0; cur != null; index++) {
if(cur.val == oldValue) {
cur.val = newValue;
return index;
}
cur = cur.next;
}
return -1;
}
链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。
链表中每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
设置虚拟头节点dummyNode,dummyNode.next 指向head,对原头节点的操作即可和其它节点统一,更加方便
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。