数据结构讨论的是数据的存储方式,常用的数据结构分为两大类,一类是线性结构,一类是非线性结构。线性结构是一对一的关系,非线性结构是一对多,或者多对多的关系。链表,栈,队列等数据结构是线性结构;树,图等数据结构是非线性结构。
链表 链表是通过指针将一组零散的内存块串连在一起,我们称这个内存块为链表的结点。结点一般有指针域和数据域两个部分,不同类型的链表,指针域有所不同。
单链表 单链表是指每个结点有且只有一个后继指针 指向下一个结点,并且最后一个结点的后继指针为 NULL 。
链表中第一个结点称之为头结点 ,最后一个结点称之为尾结点 。头结点用来记录链表的基地址,有了头结点,我们可以遍历整个链表;尾结点的后继指针为空,表示这是链表的最后一个结点。头结点不存放数据,只存放指向首结点的指针域,引入头结点的目的是为了方面链表的插入和删除操作。
结点 单链表的结点由数据域和一个指向下一个结点的指针域组成。
public class ListNode { int id; String name; ListNode next; public ListNode (int id, String name) { this .id = id; this .name = name; } }
ListNode 类定义了一个单链表的结点,id 和 name 是数据域,next 是指向下一个结点的指针域。下一个结点和当前结点一样都是 ListNode 类型。
创建单链表 头插法建立单链表
头插法建立单链表是将每次创建的结点都插入到头结点后面,作为链表头结点后面的第一个结点 。插入新结点时,要先将新插入结点的 next 指针先指向头结点的下一个结点,再将头结点的 next 指针指向新插入的结点 。如果先将头结点的 next 指针指向了新插入的结点,就会导致链表断开,这样就找不到后面的结点了。
ListNode head = new ListNode (0 , "" );public void headCreateSingleLinkedList (ListNode node) { if (head.next == null ) { head.next = node; node.next = null ; } else { node.next = head.next; head.next = node; } }
尾插法建立单链表
尾插法建立单链表是将每次创建的结点依次链接在前一个结点的后面,作为链表的尾结点 。
ListNode head = new ListNode (0 , "" );public void tailCreateSingleLinkedList (ListNode node) { ListNode temp = head; while (temp.next != null ) { temp = temp.next; } temp.next = node; node.next = null ; }
插入结点 链表插入结点不需要像数组一样移动元素,只需要修改待插入位置相邻结点的指针即可。
链表插入结点的时间复杂度为 O(1),但是链表在插入元素前需要先找到待插入的位置,而单链表只能从头结点开始按顺序往下依次查找,所以查找的时间复杂度为 O(n),综合来看,**单链表插入结点的时间复杂度为 O(n)**。
链表在插入结点时,要先将待插入结点的 next 指针指向待插入位置的下一个结点 ,然后将前一个结点的 next 指针指向待插入的结点。如果先将待插入位置前一个结点的 next 指针先指向了 待插入的结点,就会造成链表断开,后面的结点就丢失了。
public void addListNode (int index, ListNode node) { ListNode temp = head; int position = 0 ; if (head == null ) { System.out.println("链表不存在,无法插入!" ); return ; } if (index > singleLinkedListLength() || index < 0 ) { System.out.println("结点插入位置非法!" ); return ; } if (index == 0 ) { headCreateSingleLinkedList(node); return ; } while (temp.next != null ) { position++; temp = temp.next; if (position == index) { node.next = temp.next; temp.next = node; break ; } } }
删除结点 删除结点只需要将要删除结点的前一个结点的 next 指针直接指向要删除结点的下一个结点即可。
和插入结点相同,删除结点的时间复杂度为 O(1),但是查询要删除的结点时间复杂度为 O(n),所以**删除结点的时间复杂度还是 O(n)**。
插入结点只需要找到要插入的结点位置的结点即可,删除结点需要找到待删除的结点和它的前一个结点,因为需要将前一个结点的 next 指针指向待删除结点的下一个结点。
public void deleteListNode (String name) { ListNode deleteNode; ListNode temp1 = head; ListNode temp2 = head; boolean flag = false ; int position = 0 ; int previous = 0 ; if (head == null ) { System.out.println("链表不存在,无法删除!" ); return ; } if (head.next == null ) { System.out.println("链表为空,无法删除!" ); return ; } while (temp1.next != null ) { position++; temp1 = temp1.next; if (name.equals(temp1.name)) { flag = true ; break ; } } if (!flag) { System.out.println("没找到要删除的结点!" ); return ; } if (position == 1 && singleLinkedListLength() == 1 ) { head.next = null ; return ; } while (temp2.next != null ) { deleteNode = temp2.next; if (previous == position-1 ) { if (position == singleLinkedListLength()) { temp2.next = null ; return ; } temp2.next = deleteNode.next; deleteNode.next = null ; break ; } previous++; temp2 = temp2.next; } }
单链表的应用 反转单链表 单链表反转,将链表结点按顺序依次取下,按照头插法 依次插入到一个新的头结点后面,再把原来的头结点指向指向新的头结点的下一个结点即可。
ListNode head = new ListNode (0 , "" );public void reserveSingleLinkedList () { if (head.next == null || head.next.next == null ) { System.out.println("无须反转!" ); return ; } ListNode current = head.next; ListNode next; ListNode newHead = new ListNode (0 , "" ); while (current != null ) { next = current.next; current.next = newHead.next; newHead.next = current; current = next; } head.next = newHead.next; }
合并两个有序单链表 如果两个链表都不为空,依次比较大小,将较小的一个节点插入到头结点的后面,如果只有一个链表不为空,直接插到链表最后面。
public void mergeSingleLinkedList (SingleLinkedList list) { ListNode p = this .head; ListNode p1 = this .head.next; ListNode p2 = list.head.next; while (p1 != null && p2 != null ) { if (p1.id > p2.id) { p.next = p2; p2 = p2.next; } else { p.next = p1; p1 = p1.next; } p = p.next; } if (p1 != null ) { p.next = p1; } if (p2 != null ) { p.next = p2; } }
删除倒数第k个结点 倒数第 k 个结点是正数第 n-k+1 个结点,找到删除节点的前一个节点,将前一个节点指向要删除节点的下一个节点。
public void deleteKFromLastNode (int k) { ListNode p1 = head; ListNode p2 = head.next; int position = 0 ; if (p2 == null ) { System.out.println("链表为空!" ); return ; } int length = singleLinkedListLength() - k + 1 ; while (p2 != null ) { position++; if (length == position) { p1.next = p2.next; break ; } p2 = p2.next; p1 = p1.next; } }
这里也可以用双指针 实现,复杂度更低。
获取单链表中间结点 使用双指针遍历,两个指针距离两个位置,当前面的指针到达链表尾部时,后面一个指针指向的位置就是中间节点的位置。
public void getMiddleNode () { ListNode p1 = head.next; ListNode p2; if (p1 == null ) { System.out.println("链表为空!" ); return ; } p2 = p1.next; if (p2 == null ) { System.out.println("只有一个节点!" ); return ; } while (p2.next != null ) { p1 = p1.next; if (p2.next.next == null ) { break ; } p2 = p2.next.next; } System.out.println("链表的中间节点为" + p1.id + ",名称为" + p1.name); }