链表知识点:
结构:每一个节点由数据域和指针域(存放下一个节点的指针)组成,最后一个节点的指针指向null
链表的类型:
- 上述的单链表
- 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
- 循环链表:链表首尾相连。
链表的储存方式:不是连续分布的(数组是连续的),链表是通过指针域的指针链接在内存中各个节点。
链表的定义:
struct ListNode {
int val; //节点上存储的数据
ListNode *next; //存放下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
用自己定义的构造函数初始化节点:
ListNode* head = new ListNode(5); //head的数据为5
使用默认构造函数初始化节点,注意需要赋值:
ListNode* head = new ListNode();
head->val = 5;
删除节点(删除之后的要手动释放)
只要将C节点的next指针 指向E节点就可以了,再手动释放这个D节点,释放这块内存。
添加节点
203.移除链表元素
单链表删除头节点的两种方法:
- 直接使用原来的链表来进行删除操作(头节点后移)。
- 设置一个虚拟头结点在进行删除操作。
方法一:直接后移
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head!=NULL && head->val==val){
ListNode* temp; //设置一个暂时的节点 用来存放要删除的节点
temp=head;
head=head->next; //将头节点后移
delete temp;
}
//删除非头节点
ListNode* cur=head; //设置当前节点
while(cur!=NULL && cur->next!=NULL) {//确保当前节点不为空
if(cur->next->val==val){
ListNode* temp; //设置一个暂时的节点 用来存放要删除的节点
temp=cur->next;
cur->next=cur->next->next;
delete temp; //将删除的节点内存清理掉
}
else{
cur=cur->next;
}
}
return head;
}
};
方法二:增加虚拟头节点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* temphead=new ListNode(0); //创建虚拟节点
temphead->next=head; //将虚拟节点指向头节点
ListNode* cur=temphead; //当前节点
while(cur->next!=NULL){
if(cur->next->val==val){
ListNode* temp; //储存要删除的节点
temp=cur->next;
cur->next=cur->next->next;
delete temp;
}
else{
cur=cur->next; //下一个节点
}
}
head=temphead->next; //在最后返回的时候要删除虚拟节点
delete temphead;
return head;
}
};
707.设计链表
难点:这里是添加了一个虚拟头节点,链表运行的时候要知道指向的是哪个,LinkedNode* cur=_dummyHead->next时while循环结束时cur就是index的节点,当LinkedNode* cur=_dummyHead时while循环结束时cur指向的时index的前一个节点,cur->next才是index节点
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode *next;
LinkedNode(int x):val(x),next(NULL){}
};
MyLinkedList() {
_dummyHead = new LinkedNode(0); //?这里不太明白 怎么就加上虚拟头节点了
// 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
_size = 0;
}
int get(int index) {
if(index>(_size-1) || index<0) //判断下标是否有效
return -1;
LinkedNode* cur=_dummyHead->next;
while(index--){
cur=cur->next;
}
return cur->val;
}
//在具有虚拟节点的链表中将一个新的节点插入第一个元素之前
void addAtHead(int val) {
LinkedNode* newNode=new LinkedNode(val); //创建
newNode->next=_dummyHead->next; //新的节点指针指向原始的头节点
_dummyHead->next=newNode; //虚拟节点指针指向要插入的节点
_size++; //计算链表节点数
}
//将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtTail(int val) {
LinkedNode* newNode=new LinkedNode(val);
LinkedNode* cur=_dummyHead; //从头遍历一遍
while(cur->next!=NULL){ //循环找到指针为空的为止
cur=cur->next;
}
//指针为空的就是最后一个 指向新的节点
cur->next=newNode;
_size++;
}
void addAtIndex(int index, int val) {
if(index>_size)
return;
if(index<0)
index=0;
LinkedNode* newNode=new LinkedNode(val);
LinkedNode*cur=_dummyHead; //因为存在虚拟节点
while(index--){ //中止循环的时候cur就是index下标的前一个
cur=cur->next;
}
newNode->next=cur->next; //先将newNode指向cur->next 目标index
cur->next=newNode;
_size++;
}
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur=_dummyHead;
while(index--){
cur=cur->next;
}
LinkedNode* temp=cur->next;
cur->next=cur->next->next;
delete temp;
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
temp=nullptr;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
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);
*/
206.反转链表
思路:直接将指针指向反转,不用重新定义新的链表
双指针法:利用双指针改变指向 递归法:还没掌握
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre=NULL;
ListNode* cur=head;
ListNode* temp;
while(cur){
temp=cur->next; // 保存原来方向的next
cur->next=pre; //将指向反转
pre=cur; //双指针向前移动
cur=temp;
}
return pre;
}
};