数组和链表
数组
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
数组的插入、删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移,所以时间复杂度是O(n)。
链表
单链表
在链表中插入或者删除一个数,只需要考虑相邻结点的指针改变,所以对应的时间复杂度是O(1)
链表随机访问性能没有数组好,需要 O(n) 的时间复杂度。
循环链表
双向链表
双向链表需要额外的两个空间存储后继节点和前驱节点的地址,但可以支持双向遍历。
从结构上看,双向链表可以支持 O(1) 时间复杂度的情况下找到前驱节点,再某些情况下插入、删除等操作都要比单链表简单、高效。
双向循环链表
数组 VS 链表
内存分布
| 操作 | 数组 | 链表 |
|---|---|---|
| 访问 | O(1):直接下标访问 | O(n):需要遍历元素 |
| 内存 | 大小固定,一组连续的内存空间 | 需要额外的指针,内存空间不用连续 |
| 插入 | 1. 头部插入:O(n) ,需要将每个元素向高位移动一个位置 2. 尾部插入:数据未满O(1);数组已满O(n),创建一个新的数组并复制所有的内容。 3. 中间插入:O(n),需要移动数据,平均情况下,时间与n成正比。 | 1. 头部插入:O(1),创建一个新节点,并调整头指针和该新节点的链接。 2. 尾部插入:O(n),需要遍历到尾部。 3. 中间插入:O(n),需要遍历到该位置然后调整链接。 |
| 删除 | 1. 头部删除:O(n) ,需要将每个元素向低位移动一个位置 2. 尾部删除:不缩减数组 O(1);缩减数组 O(n),创建一个新的数组并复制所有的内容。 3. 中间删除:O(n),需要移动数据,平均情况下,时间与n成正比。 | 1. 头部删除:O(1),调整头指针链接。 2. 尾部删除:O(n),需要遍历到尾部。 3. 中间删除:O(n),需要遍历到该位置然后调整链接。 |
| 使用 | 简单 | 复杂,尤其在C/C++中更容易出现错误,比如段错误和内存泄漏。 |
链表反转
迭代实现
public ListNode reverseList(ListNode head) {
if(head == null) {
return head;
}
ListNode reverse = null;
ListNode current = head;
while(current != null) {
ListNode next = current.next;
current.next = reverse;
reverse = current;
current = next;
}
return reverse;
}
递归实现
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode last = reverseList(head.next);
head.next.next = head;
head.next = null;
return last;
}