707. 设计链表 链表的知识复习
707. 设计链表
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode *next;
LinkedNode(int val):val(val),next(nullptr){}
};
MyLinkedList(){
dummyhead = new LinkedNode(0);
size=0;
}
int get(int index) {
if(index < 0 || index >= size){
return -1;
}
LinkedNode * cur = dummyhead->next;//注意这里cur指针的指向是哪儿,画图判断逻辑是否是正确的。
while(index--){
//初始化要放在外面 ListNode * cur = dummyhead;
cur = cur->next;
}
// return cur->next->val;
return cur->val;
}
void addAtHead(int val) {
LinkedNode * newNode = new LinkedNode(val);
newNode->next = dummyhead->next;
dummyhead->next = newNode;
size++;
}
void addAtTail(int val) {
LinkedNode *newNode = new LinkedNode(val);
LinkedNode *cur = dummyhead;
while(cur->next != nullptr){
cur= cur->next;
}
cur->next = newNode;
size++;
}
void addAtIndex(int index, int val) {
if(index > size || size < 0) return ;
// if(index == size ){
// addAtTail(val);
// return;
// }
LinkedNode * newNode = new LinkedNode(val);
LinkedNode *cur=dummyhead;
while(index--){
cur=cur->next;
}
newNode->next = cur->next;
cur->next= newNode;
size++;
}
void deleteAtIndex(int index) {
if(index<0||index>= size){
return;
}
LinkedNode*cur=dummyhead;
while(index--){
cur= cur->next;
}
LinkedNode *temp= cur->next;
cur->next = cur->next->next;
delete temp;
size--;
temp =nullptr;
}
private:
int size;
LinkedNode * dummyhead;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
这三个构造函数在 ListNode
结构体中定义的作用和用法有所不同,它们分别处理了不同的初始化场景。我们来逐一分析这三个构造函数的区别:
1. 默认构造函数
ListNode() : val(0), next(nullptr) {}
-
作用:这是一个 默认构造函数,用于在不提供任何参数的情况下创建
ListNode
对象。 -
初始化:
val(0)
:将节点的val
初始化为0
。next(nullptr)
:将节点的next
指针初始化为nullptr
,意味着该节点不指向任何其他节点。
-
用途:如果你只是创建一个空的节点(没有指定值或下一个节点),使用这个构造函数非常方便。比如:
ListNode node; // 创建一个 val 为 0, next 为 nullptr 的节点
2. 带一个参数的构造函数
ListNode(int x) : val(x), next(nullptr) {}
-
作用:这是一个 带一个参数的构造函数,用于在创建节点时指定
val
的值,但不指定next
,即将next
指向nullptr
。 -
初始化:
val(x)
:将节点的val
初始化为传入的参数x
。next(nullptr)
:将节点的next
指针初始化为nullptr
,意味着该节点没有连接到其他节点。
-
用途:当你创建一个节点,并且只关心它的值而不关心它的
next
指针时使用这个构造函数。例如:ListNode node(5); // 创建一个 val 为 5, next 为 nullptr 的节点
3. 带两个参数的构造函数
ListNode(int x, ListNode* next) : val(x), next(next) {}
-
作用:这是一个 带两个参数的构造函数,用于在创建节点时同时指定
val
和next
。 -
初始化:
val(x)
:将节点的val
初始化为传入的参数x
。next(next)
:将节点的next
指针初始化为传入的参数next
,即指向另一个ListNode
对象。这个参数通常是指向另一个链表节点的指针,表示该节点的 "下一个" 节点。
-
用途:当你需要创建一个包含特定值且与其他节点连接的节点时使用。例如:
ListNode* node3 = new ListNode(3); ListNode* node2 = new ListNode(2, node3); // 创建一个 val 为 2 的节点,它的 next 指向 node3
在这个例子中,
node2
的val
是2
,next
是指向node3
的指针,node3
的val
是3
,next
为nullptr
。
总结
- 默认构造函数:不带任何参数,创建一个
val
为0
,next
为nullptr
的节点。 - 带一个参数的构造函数:只传入一个
val
参数,next
默认为nullptr
。常用于创建值已知但不关心next
的节点。 - 带两个参数的构造函数:同时传入
val
和next
,用于创建值已知并且与其他节点连接的节点。
这三种构造函数提供了不同的初始化方式,使得你可以灵活地创建和管理链表节点,特别是在处理链表时。
是的,虚拟头节点(dummy head)会作为链表中的第一个节点,但它的作用并不是作为链表数据的 "实际头节点"。它的存在是为了简化链表的操作,尤其是在插入和删除节点时避免处理空链表或单节点链表时的特殊情况。
虚拟头节点的作用
在链表中,特别是单向链表,在执行插入和删除操作时,往往需要特殊处理头节点,尤其是在头节点是空时。例如:
- 删除头节点:你需要把头节点的指针指向第二个节点。
- 插入新节点到头部:你需要更新头节点的指针。
这些操作如果没有虚拟头节点,可能会导致一些复杂的边界条件需要单独处理,增加代码的复杂性。
通过虚拟头节点,简化了这些边界条件的处理:
- 无论链表是否为空,操作都变得统一。虚拟头节点总是存在的,插入和删除操作都可以直接从虚拟头节点开始,避免了处理空链表或只有一个节点的特殊情况。
虚拟头节点的工作原理
-
初始化时:
MyLinkedList()
构造函数中通过new LinkedNode(0)
创建一个值为0
的虚拟头节点,并将_dummyHead
指向它。- 这个节点的
next
指针初始为nullptr
,表示链表为空。
-
链表的操作:
- 例如,当你插入一个节点时,不需要检查链表是否为空,只需从虚拟头节点开始。
- 例如,在插入新节点时(
addAtHead
),你只需将虚拟头节点的next
指针指向新节点。 - 这样,无论链表是空的,还是有节点,代码的逻辑都很简单。
虚拟头节点如何作为第一个节点?
即使虚拟头节点是链表中的第一个节点,它本身并不存储有效数据。它的 val
通常不被使用或不重要,它只是为了简化链表操作而存在。
- 链表中的第一个有效节点 是紧随虚拟头节点之后的节点。虚拟头节点只是在链表结构中占据一个位置,用来保持链表的一致性和简化操作。
举个例子
假设你通过 addAtHead
函数添加一个新节点 5
到链表中:
list.addAtHead(5); // 向链表头部添加一个节点,值为 5
在调用 addAtHead(5)
后,链表结构会变成:
虚拟头节点 (val=0) → 新节点 (val=5) → nullptr
- 这里的虚拟头节点的值
0
是无关紧要的,它的作用只是作为链表的 "头" 节点。 - 第一个有效的节点是
val=5
的节点,它是链表中的实际数据节点。
如何理解链表中的头节点
- 链表的头节点(head node)通常指向链表中的第一个有效数据节点。
- 虚拟头节点(dummy head)是为了方便链表操作而引入的,通常它的值是无关紧要的。
总结
- 虚拟头节点 在
MyLinkedList
类中是一个特殊的辅助节点,它是链表结构的一部分。 - 在初始化时,虚拟头节点被创建并赋值给
_dummyHead
,但它并不包含有效数据。 - 链表的第一个有效数据节点 是虚拟头节点的
next
指向的节点。 - 通过引入虚拟头节点,链表的插入和删除操作变得更简洁、统一。