数据结构学习成果线性表部分成果分享:
此博客共分两个部分:基本概念及java实现、部分具体问题的代码实现
第一部分:
基本概念:
线性表是具有相同数据结构类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表时一个空表。若用L命名线性表,则其一般表示为:
L = {a1,a2,........ai.......an}
性质:1.除第一个元素外,每一个元素有且仅有一个直接前驱;
2.除最后一个元素外,每个元素有且仅有一个直接后继。
3.每个元素占有相同大小的存储空间。
分类:线性表这种线性结构共有两种存储结构:
(1)顺序存储结构(顺序表)
性质:逻辑上相邻的两个元素在物理位置上也相邻,即为一组地址连续的存储单元。
实现: 在java中可用实现了List接口的ArrayList类实现,ArrayList其实是一个动态数组,也是我们常用的集合,它允许任何元素的插入,甚至包括null。每一个ArrayList都有一个初始化的容量(0),该容量代表了数组的大小,随着容器中容量的不断增加,容器的大小也会随着增加。在每次向容器中增加元素时,会进行容量检查,当快溢出时,会进行扩容 *** 作,第一次扩容的大小为10,后来每一次均为前一次容量的1.5倍。
(2) 链式存储结构(链表)
性质: 逻辑上相邻的两个元素在物理位置上不一定连续,即通过一组任意的存储单元来存储线性 表中的数据元素。
链表的分类:
1.单链表:
2. 双向链表:
3.循环链表:
总结:1.一个节点分为数据域和指针域,头节点的数据域可以不设任何信息,
也可以记录表长等信 息,双向链表有两个指针域,分别指向前驱节点和后继节点
2.头节点可有可无,若无头节点,则头指针直接指向第一个节点
3.链表为空的表现:有头结点时则为头结点的下一个节点为null,无头结点则为头指针为null
循环链表为头节点的下一个节点为头节点
实现:在java中可用实现了List接口的LinkedList类实现链表结构,LinkedList 的底层使用的是双向链表的存储结构, 在 LinkedList 内部,有 size first last 属性以及一个内部类 Node
节点手动代码实现:
public class Node{
Node prev;
Node next;
E value;
public Node(Node prev,Node next,E value){
this.prev = prev;
this.next = next;
this.value = value;
}
}
ArrayList和LinkedList部分共有方法:
1.boolean add(E e) 列表末尾添加元素,返回是否成功,成功为 true,失败为 false。
2.int size() 返回此列表中的元素数
3.E get(int index) 返回此列表中指定位置的元素
4.E remove(int index) 删除该列表中指定位置的元素
5.void addFirst(E e) 在该列表开头插入指定的元素
6.void addLast(E e) 将指定的元素追加到此列表的末尾
7.boolean contains(Object o) 如果此列表包含指定的元素,则返回 true
8.void clear() 从列表中删除所有元素
顺序表与链表的比较:
1.存取(读写)方式
顺序表可以顺序存取,也可以随机存取,而链表只能从表头顺序存取元素。
2.查找、插入和删除 *** 作
对于按值查找,顺序表无序时,两者的时间复杂度均为O(N);顺序表有序时,可采用折半查找,此时的时间复杂度为O(logN)。
对于按序号查找,顺序表支持随机访问,时间复杂度仅为O(1),而链表的平均时间复杂度为O(N)。顺序表的插入、删除 *** 作,平均需要移动半个表长的元素。链表的插入、删除 *** 作,只需修改相关节点的指针域即可。由于链表的每个节点都带有指针域,故而存储密度不大。
3.空间分配
顺序表动态存储分配虽然存储空间可以扩充,但需要移动大量元素,导致 *** 作效率降低,而且若内存中没有更大块的连续存储空间,则会导致分配失败。链式存储的节点空间只在需要时申请分配,只要内存有空间就可以分配, *** 作灵活、高效。
第二部分:
节点代码:
public class ListNode {
int val;
ListNode next;
ListNode random; //随机指针
ListNode() {}
ListNode(int val){this.val = val;}
ListNode(int val,ListNode next,ListNode random) {
this.val = val;
this.next = null;
this.random = null;
}
}
以下题目和代码均是看左神的算法教学视频所得!
问题一:
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔 分为小于X 等于X 大于x三部分 你应当 保留 两个分区中每个节点的初始相对位置。
代码:
public ListNode partition(ListNode head, int x) {
ListNode sH = null;
ListNode sT = null;
ListNode eH = null;
ListNode eT = null;
ListNode bH = null;
ListNode bT = null;
ListNode next = null;
while(head!=null){
next = head.next;
head.next = null;
if(head.val
分析: 先给出六个空节点,分别为小于区域的头和尾节点、等于区域的头和尾节点、大于区域的头和尾节点,遍历该链表,利用while循环形成三个链表即三个区域所属数的保留原始位置的子链表,然后三个链表首位相接即可。
问题二:
给你两个单链表的头节点 headA 和 headB,请你找出并返回两个单链表相交的起始节点。 如果两个链表没有交点,返回 null 。
代码:
public static ListNode getIntersectNode(ListNode head1,ListNode head2){
if (head1==null||head2==null){
return null;
}
ListNode loop1 = getLoopNode(head1);
ListNode loop2 = getLoopNode(head2);
if (loop1==null&&loop2==null){
noLoop(head1,head2);
}
if (loop1!=null&&loop2!=null){
return bothLoop(head1,head2,loop1,loop2);
}
return null;
}
//判断一个单链表是否有环存在 并且返回入环节点
public static ListNode getLoopNode(ListNode head){
if (head == null|| head.next == null){
return null;
}
ListNode ln1 = head.next;
ListNode ln2 = head.next.next;
while (ln1!=ln2){
if (ln2.next==null||ln2.next.next==null){
return null;
}
ln2 = ln2.next.next;
ln1 = ln1.next;
}
ln2 = head;
while (ln1!=ln2){
ln1 = ln1.next;
ln2 = ln2.next;
}
return ln1;
}
//无环情况
public static ListNode noLoop(ListNode head1,ListNode head2){
if (head1==null||head2==null){
return null;
}
ListNode cur1 = head1;
ListNode cur2 = head2;
int n = 0;
while (cur1.next!=null){
n++;
cur1 = cur1.next;
}
while (cur2.next!=null){
n--;
cur2 = cur2.next;
}
if (cur1!=cur2){
return null;
}
cur1 = n>0 ? head1:head2;
cur2 = cur1==head1 ? head2:head1;
n = Math.abs(n);
while (n!=0){
n--;
cur1 = cur1.next;
}
while (cur1!=cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
//均有环情况
public static ListNode bothLoop(ListNode head1,ListNode head2,ListNode loop1,ListNode loop2){
ListNode cur1 = null;
ListNode cur2 = null;
if (loop1==loop2){
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1!=loop1){
n++;
cur1 = cur1.next;
}
while (cur2!=loop2){
n--;
cur2 = cur2.next;
}
cur1 = n>0 ? head1:head2;
cur2 = cur1==head1 ? head2:head1;
n = Math.abs(n);
while (n!=0){
n--;
cur1 = cur1.next;
}
while (cur1!=cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else {
cur1 = loop1.next;
while (cur1!=loop1){
if (cur1==loop2){
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
分析:
两链表存在相交的三种情况:
1.无环情况下只需记录较长链表与较短链表的长度差,然后先让较长链表遍历完长度为长度差的部分,然后两个链表一起遍历,相遇的节点即为相交的起始节点。
2.第一种有环情况思想与无环情况相似,先判断两个链表的如环节点是否为同一点来区分第二和第三种情况,若为同一点,即为第二种情况,然后用无环情况的获取起始节点相同的方法来得到此时相交的起始节点。
3.最后一种情况更简单,若入环节点不是同一个节点,则返回任意一个入环节点即可。
关键:如何判断一个有环链表的入环节点?
让慢指针一次走一步,快指针一次走两步,当快慢指针在环中相遇时,记录下这个相遇点M。然后我们定义指针cur从链表头开始走,让刚才在相遇点的slow指针也开始走,他们每次都走一步,他们的相遇点一定为环的入口点。(这是一个可证明的事实)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)