今日任务
- 链表理论基础
- 203.移除链表元素
- 707.设计链表
- 206.反转链表
链表理论基础
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
如图所示: 链表1
链表的类型
单链表
双链表
单链表中的指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
如图所示:
循环链表
链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
链表的定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
也可以不定义构造函数,C++会默认生成一个构造函数,但他不会初始化任何成员变量。
- 通过自己定义构造函数初始化节点:
ListNode * head = new ListNode(5);
- 使用默认构造函数初始化节点
ListNode* head = new ListNode();
head->val = 5;
链表的操作
删除节点
在c++中需要手动释放D节点,释放此块内存。
添加节点
性能分析(对比数组)
203.移除链表元素 (简单)
题目链接:
203.移除链表元素
**题目描述:**删除链表中等于给定值 val 的所有节点。
方法一:头节点和非头节点分别处理
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//删除头节点
while(head != NULL && head->val == val){//若头节点不为空且值为val
ListNode* q = head; //用q记录要删除的节点
head = head->next; //由于是头节点,故head后移便是在逻辑上删除了该节点
delete q; //释放内存
}
//删除非头节点
ListNode* p = head; //用p作为遍历指针
while(p != NULL && p->next != NULL){//若p和p的下一个节点不为空
if(p->next->val == val){ //若p的下一个节点值等于val
ListNode* q = p->next;//用q记录要删除的节点
p->next = p->next->next;//逻辑上删除
delete q;// 释放内存
}else{
p = p->next;//否则接着遍历
}
}
return head;
}
};
注:C++中释放节点内存 直接delete 即可。
方法二:设一个虚拟头节点来统一头节点和非头节点的操作。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//使用虚拟头节点
ListNode* Lhead = new ListNode(0);
Lhead->next = head;
ListNode* p = Lhead;
while(p->next != NULL){
if(p->next->val == val){
ListNode* q = p->next;
p->next = p->next->next;
delete q;
}
else{
p = p->next;
}
}
head = Lhead->next;//虚拟头节点需要做进一步的处理,head即Lhead->next,再释放虚拟头节点Lhead
delete Lhead;
return head;
}
};
注1:这里涉及C++结构体(struct)的使用,定义与声明和C区别不大,主要的区别在于C++中可以使用new动态创建结构体变量,此时必须是结构体指针类型。访问时,普通结构体变量使用成员变量访问符为".“,指针类型的结构体变量使用的成员便令访问符为”->"。
#include <iostream>
using namespace std;
struct Student
{
int Code;
char Name[20];
char Sex;
int Age;
}Stu,StuArray[10],*pStu;
int main(){
Student *s = new Student(); // 或者Student *s = new Student;
s->Code = 1;//指针类型访问成员变量使用->
cout<<s->Code;
delete s;
return 0;
}
注2:结构体初始化方法有以下三种:
1.利用结构体自带的默认构造函数
2.利用带参数的构造函数
3.利用默认无参的构造函数
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}//其实就是无参构造函数
ListNode(int x) : val(x), next(nullptr) {}//只传val的单参构造函数
ListNode(int x, ListNode *next) : val(x), next(next) {}//有参构造函数
};
707.设计链表(中等)
题目链接:707.设计链表
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode() : val(0), next(nullptr){}
LinkedNode(int val) : val(val), next(nullptr){}
};
//初始化链表
MyLinkedList(){
_dummyhead = new LinkedNode(0);//一个虚拟头节点,并非真正的头节点
_size = 0;//链表大小
}
//取第index个节点
int get(int index) {
if(index >= _size || index < 0) {
return -1;
}
LinkedNode* LNode = _dummyhead->next;//指向第一个非空节点,
while(index--){//
LNode = LNode->next;
}
return LNode->val;
}
//头插法插入新的节点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);//创建一个值为val新节点
newNode->next = _dummyhead->next;
_dummyhead->next = newNode;
_size++;
}
//尾插法插入新的节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* p = _dummyhead;
while(p->next != nullptr){//切记是判断p->next是否为空指针
p = p->next;
}
p->next = newNode;
_size++;
}
//在某个节点前插入新节点
void addAtIndex(int index, int val) {
if(index > _size) return;//若index大于链表长度,直接返回
if(index < 0)index = 0;//index小于0,则直接头插
LinkedNode* preNode = _dummyhead;
LinkedNode* newNode = new LinkedNode(val);
while( index--){
preNode = preNode->next;
}
newNode->next = preNode->next;
preNode->next = newNode;
_size++;
}
//删除第index个节点
void deleteAtIndex(int index) {
if(index >= _size || index < 0){
return;
}else{
LinkedNode* pNode = _dummyhead;
while(index--) {//至此找到index-1处的节点
pNode = pNode->next;
}
LinkedNode* qNode = pNode->next;//qNode指向要删除节点
pNode->next = pNode->next->next;
delete qNode;
_size--;
}
}
//打印链表
void printLinkedList(){
LinkedNode* p = _dummyhead;
while (p->next != nullptr){
cout << p->next->val << " ";
p = p->next;
}
cout << endl;
}
//定义成员变量
private:
int _size;
LinkedNode* _dummyhead;
};
注:在编写链表的初始化方法时,声明一个虚拟头节点和链表的长度初值作为链表的成员变量,变量应该是private类型的,置于末尾是为了不截断public中的方法。
206.反转链表
题目链接:206.反转链表
题目建议:用迭代或者递归做?
方法一:双指针法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;//前指针,需要赋初值为NULL,否则会报错
ListNode* p;//用于存放cur->next的指针
ListNode* cur = head;
while(cur) {
p = cur->next;
cur->next = pre;
pre = cur;
cur = p;
}//至此p和cur指针均指向NULL,pre指向反转后的链头节点
return pre;
}
};
方法二:递归法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){//递归体
if(cur == NULL) return pre;//当前指针为NULL,直接返回pre
ListNode* temp = cur->next;//temp记录cur->next指针
cur->next = pre;//反转指针方向
return reverse(cur,temp);//依次向后循环
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL, head);//递归的开头NULL和头节点
}
};