1、数组
1)、数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据
2)、线性表与非线性表
- 线性表:数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构
- 非线性表:数据之间并不是简单的前后关系。比如二叉树、堆、图等
3)、连续的内存空间和相同类型的数据,正是因为这两个限制,数组才有了随机访问的特性,但在删除、插入一个数据时,为了保证连续性,就需要做大量的数据搬移工作
4)、数组支持随机访问,根据下标随机访问的时间复杂度为 ;链表适合插入、删除,时间复杂度为
5)、ArrayList支持动态扩容,每次存储空间不够的时候,它会将空间自动扩容为1.5倍的大小。因为扩容操作涉及内存申请和数据搬移比较耗时。所以,如果事先能确定需要存储的数据大小,最好在创建ArrayList的时候事先指定数据大小
6)、ArrayList无法存储基本类型,比如int、long,需要封装为Integer、Long类,而自动装箱拆箱则有一定的性能消耗,所以如果特别关注性能,或者希望使用基本类型,就可以选用数组
/*
* 1) 数组的插入、删除、按照下标随机访问操作;
* 2) 数组中的数据是int类型的;
*/
public class Array {
// 定义整型数据data保存数据
public int data[];
// 定义数组长度
private int n;
// 定义中实际个数
private int count;
// 构造方法,定义数组大小
public Array(int capacity) {
this.data = new int[capacity];
this.n = capacity;
this.count = 0;// 一开始一个数都没有存所以为0
}
// 根据索引,找到数据中的元素并返回
public int find(int index) {
if (index < 0 || index >= count)
return -1;
return data[index];
}
// 插入元素
public boolean insert(int index, int value) {
// 数组空间已满
if (count == n) {
System.out.println("没有可插入的位置");
return false;
}
// 如果count还没满,那么就可以插入数据到数组中
// 位置不合法
if (index < 0 || index > count) {
System.out.println("位置不合法");
return false;
}
// 位置合法
for (int i = count; i > index; --i) {
data[i] = data[i - 1];
}
data[index] = value;
++count;
return true;
}
// 根据索引,删除数组中元素
public boolean delete(int index) {
if (index < 0 || index >= count)
return false;
// 从删除位置开始,将后面的元素向前移动一位
for (int i = index + 1; i < count; ++i) {
data[i - 1] = data[i];
}
--count;
return true;
}
public void printAll() {
for (int i = 0; i < count; ++i) {
System.out.print(data[i] + " ");
}
System.out.println();
}
}
2、链表
1)、链表不需要一块连续的内存空间,它通过指针将一组零散的内存块串联起来使用
2)、单链表:链表通过指针将一组零散的内存块串联在一起,我们把内存块称为链表的结点。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。把这个记录下个结点地址的指针叫作后继指针next
3)、单链表中两个结点比较特殊,分别是第一个结点和最后一个结点。第一个结点叫作头结点,用来记录链表的基地址,通过它可以遍历得到整条链表。最后一个结点叫作尾节点,指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点
4)、链表的插入删除操作时间复杂度是 ,根据下标随机访问的时间复杂度为
5)、循环链表:循环链表的尾节点指针是指向链表的头结点,优点是从链尾到链头比较方便,当要处理的数据具有环型结构特点时,就适合采用循环链表
6)、双向链表:支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱结点prev指向前面的结点
7)、双向链表需要额外的两个空间来存储后继结点和前驱结点的地址,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间,但可以支持双向遍历
8)、LinkedHashMap是使用双向链表实现的
9)、链表和数组的对比
数组简单易用,在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储的,所以对CPU缓存不友好,没办法有效预读
数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致内存不足。如果声明的数组过小,则可能出现不够用的情况,这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常耗时。链表本身没有大小的限制,天然地支持动态扩容
如果代码对内存的使用非常苛刻,那数字就比较适合。因为链表中的每个结点都需要消耗额外的存储空间去存储一份指向下一个结点的指针,所以内存消耗会翻倍。而且,对于链表进行频繁的插入、删除操作,还会导致频繁的内存申请和释放,容易造成内存碎片,如果是Java语言,就有可能导致频繁的GC
/**
* 1)单链表的插入、删除、查找操作;
* 2)链表中存储的是int类型的数据;
*/
public class SinglyLinkedList {
private Node head = null;
public Node findByValue(int value) {
Node p = head;
while (p != null && p.data != value) {
p = p.next;
}
return p;
}
public Node findByIndex(int index) {
Node p = head;
int pos = 0;
while (p != null && pos != index) {
p = p.next;
++pos;
}
return p;
}
//无头结点
//表头部插入
//这种操作将于输入的顺序相反,逆序
public void insertToHead(int value) {
Node newNode = new Node(value, null);
insertToHead(newNode);
}
public void insertToHead(Node newNode) {
if (head == null) {
head = newNode;
} else {
newNode.next = head;
head = newNode;
}
}
//顺序插入
//链表尾部插入
public void insertTail(int value){
Node newNode = new Node(value, null);
//空链表,可以插入新节点作为head,也可以不操作
if (head == null){
head = newNode;
}else{
Node q = head;
while(q.next != null){
q = q.next;
}
newNode.next = q.next;
q.next = newNode;
}
}
public void insertAfter(Node p, int value) {
Node newNode = new Node(value, null);
insertAfter(p, newNode);
}
public void insertAfter(Node p, Node newNode) {
if (p == null) return;
newNode.next = p.next;
p.next = newNode;
}
public void insertBefore(Node p, int value) {
Node newNode = new Node(value, null);
insertBefore(p, newNode);
}
public void insertBefore(Node p, Node newNode) {
if (p == null) return;
if (head == p) {
insertToHead(newNode);
return;
}
Node q = head;
while (q != null && q.next != p) {
q = q.next;
}
if (q == null) {
return;
}
newNode.next = p;
q.next = newNode;
}
public void deleteByNode(Node p) {
if (p == null || head == null) return;
if (p == head) {
head = head.next;
return;
}
Node q = head;
while (q != null && q.next != p) {
q = q.next;
}
if (q == null) {
return;
}
q.next = q.next.next;
}
public void deleteByValue(int value) {
if (head == null) return;
Node p = head;
Node q = null;
while (p != null && p.data != value) {
q = p;
p = p.next;
}
if (p == null) return;
if (q == null) {
head = head.next;
} else {
q.next = q.next.next;
}
}
public void printAll() {
Node p = head;
while (p != null) {
System.out.print(p.data + " ");
p = p.next;
}
System.out.println();
}
//判断true or false
public boolean TFResult(Node left, Node right){
Node l = left;
Node r = right;
System.out.println("left_:"+l.data);
System.out.println("right_:"+r.data);
while(l != null && r != null){
if (l.data == r.data){
l = l.next;
r = r.next;
continue;
}else{
break;
}
}
System.out.println("什么结果");
if (l==null && r==null){
System.out.println("什么结果");
return true;
}else{
return false;
}
}
// 判断是否为回文
public boolean palindrome(){
if (head == null){
return false;
}else{
System.out.println("开始执行找到中间节点");
Node p = head;
Node q = head;
if (p.next == null){
System.out.println("只有一个元素");
return true;
}
while( q.next != null && q.next.next != null){
p = p.next;
q = q.next.next;
}
System.out.println("中间节点" + p.data);
System.out.println("开始执行奇数节点的回文判断");
Node leftLink = null;
Node rightLink = null;
if(q.next == null){
// p 一定为整个链表的中点,且节点数目为奇数
rightLink = p.next;
leftLink = inverseLinkList(p).next;
System.out.println("左边第一个节点"+leftLink.data);
System.out.println("右边第一个节点"+rightLink.data);
}else{
//p q 均为中点
rightLink = p.next;
leftLink = inverseLinkList(p);
}
return TFResult(leftLink, rightLink);
}
}
//带结点的链表翻转
public Node inverseLinkList_head(Node p){
// Head 为新建的一个头结点
Node Head = new Node(9999,null);
// p 为原来整个链表的头结点,现在Head指向 整个链表
Head.next = p;
/*
带头结点的链表翻转等价于
从第二个元素开始重新头插法建立链表
*/
Node Cur = p.next;
p.next = null;
Node next = null;
while(Cur != null){
next = Cur.next;
Cur.next = Head.next;
Head.next = Cur;
System.out.println("first " + Head.data);
Cur = next;
}
// 返回左半部分的中点之前的那个节点
// 从此处开始同步像两边比较
return Head;
}
//无头结点的链表翻转
public Node inverseLinkList(Node p){
Node pre = null;
Node r = head;
System.out.println("z---" + r.data);
Node next= null;
while(r !=p){
next = r.next;
r.next = pre;
pre = r;
r = next;
}
r.next = pre;
// 返回左半部分的中点之前的那个节点
// 从此处开始同步像两边比较
return r;
}
public static Node createNode(int value) {
return new Node(value, null);
}
public static class Node {
private int data;
private Node next;
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
public int getData() {
return data;
}
}
}
/**
* 1) 单链表反转
* 2) 链表中环的检测
* 3) 两个有序的链表合并
* 4) 删除链表倒数第n个结点
* 5) 求链表的中间结点
*/
public class LinkedListAlgo {
// 单链表反转
public static Node reverse(Node list) {
Node headNode = null;
Node previousNode = null;
Node currentNode = list;
while (currentNode != null) {
Node nextNode = currentNode.next;
if (nextNode == null) {
headNode = currentNode;
}
currentNode.next = previousNode;
previousNode = currentNode;
currentNode = nextNode;
}
return headNode;
}
// 检测环
public static boolean checkCircle(Node list) {
if (list == null) return false;
Node fast = list.next;
Node slow = list;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) return true;
}
return false;
}
// 有序链表合并
public static Node mergeSortedLists(Node la, Node lb) {
if (la == null) return lb;
if (lb == null) return la;
Node p = la;
Node q = lb;
Node head;
if (p.data < q.data) {
head = p;
p = p.next;
} else {
head = q;
q = q.next;
}
Node r = head;
while (p != null && q != null) {
if (p.data < q.data) {
r.next = p;
p = p.next;
} else {
r.next = q;
q = q.next;
}
r = r.next;
}
if (p != null) {
r.next = p;
} else {
r.next = q;
}
return head;
}
// 删除倒数第K个结点
public static Node deleteLastKth(Node list, int k) {
Node fast = list;
int i = 1;
while (fast != null && i < k) {
fast = fast.next;
++i;
}
if (fast == null) return list;
Node slow = list;
Node prev = null;
while (fast.next != null) {
fast = fast.next;
prev = slow;
slow = slow.next;
}
if (prev == null) {
list = list.next;
} else {
prev.next = prev.next.next;
}
return list;
}
// 求中间结点
public static Node findMiddleNode(Node list) {
if (list == null) return null;
Node fast = list;
Node slow = list;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
public static void printAll(Node list) {
Node p = list;
while (p != null) {
System.out.print(p.data + " ");
p = p.next;
}
System.out.println();
}
public static Node createNode(int value) {
return new Node(value, null);
}
public static class Node {
private int data;
private Node next;
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
public int getData() {
return data;
}
}
}