提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
今天主要学习了四个部分,链表的理论知识,移除链表元素,设计链表和翻转列表。内容比较多,花了一下午加一晚上的时间,第二天才做的总结,对链表的知识有了比较细致的了解。
一、链表的理论知识
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)
链表的类型分为单链表和双链表,单链表中的指针域只能指向节点的下一个节点。双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表 既可以向前查询也可以向后查询。
循环链表:链表可以收尾相连。
链表和数组的区别:数组再内存中连续分布,而链表再内存中不是连续分布,如图所示,这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。
链表的定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
数组和链表的使用区别:
数组 | 链表 |
---|---|
长度固定,频繁查询,较少增删 | 长度不固定,频繁增删,较少查询 |
二、移除链表元素
1.使用原来的链表来删除操作
思路:首先就是先保证头节点不能是索要的元素,因为后面都是把下一个节点变成下下个节点,没考虑本节点要不要删除,然后确定了头节点不用删除后,后面就是如果下一个节点要删除,那么下一个节点的指向是下下个节点,此处要保证下一个节点不能为空,因为为空的话,下一个节点变成下下个节点就成了空节点变成空节点没意义,如果是下下个节点为空,是可以的,因为如果最后一个节点需要删除,那么最后一个节点是下一个节点,直接下一个节点是下下个节点(空节点),不指向原本的最后一个节点了,没问题,因为本来最后一个就要指向非空节点。
代码如下(示例):
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head!=NULL&&head->val==val)
{
ListNode* too=head;
head=head->next;
delete too;
}
ListNode* cur=head;
while(cur!=NULL&&cur->next!=NULL)
{ if(cur->next->val==val)
{ ListNode* tool=cur->next;
cur->next=cur->next->next;
delete tool;
}
else
{
cur=cur->next;
}
}
return head;
}
};
注意,如果我们要取一个节点的值,那么这个节点不能是空指针,同样如果我们要取cur-next的值,注意cur和cur-next都不能为空
需要释放内存的话,让一个随便设置的指针等于这个指针,然后让这个指针等于别的,最后删除随便设置的那个指针,就可以把原来那个指针所存储的元素给删掉了。
2.设置虚拟头节点来进行删除操作
思路:和之前的类似,注意两个点,一个是dummyHead->next=head;而不是head=dummyHead->next,前者是把dummyHead的下一个节点指向head,下一个是head节点指向dummyHead的下一个节点,注意的第二个点就是虚拟头节点的建立。ListNode* dummyHead = new ListNode(0);建立了一个新的节点,值为0,节点名称叫dummyHead
代码如下(示例):
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next=head;
ListNode* cur=dummyHead;
while(cur!=NULL&&cur->next!=NULL)
{ if(cur->next->val==val)
{ ListNode* tool=cur->next;
cur->next=cur->next->next;
delete tool;
}
else
{
cur=cur->next;
}
}
return dummyHead->next;
delete dummyHead;
}
};
和之前的类似,注意两个点,一个是dummyHead->next=head;而不是head=dummyHead->next,前者是把dummyHead的下一个节点指向head,下一个是head节点指向dummyHead的下一个节点,注意的第二个点就是虚拟头节点的建立:ListNode* dummyHead = new ListNode(0);
建立了一个新的节点,值为0,节点名称叫dummyHead
三、设计链表
代码:
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-1))
{
return -1;
}
LinkedNode *cur=dummyHead->next;
while(index!=0)
{
index--;
cur=cur->next;
}
return cur->val;//记得他让你返回什么
}
void addAtHead(int val)
{
LinkedNode *newnode=new LinkedNode(val);
newnode->next=dummyHead->next;
dummyHead->next=newnode;
size++;
//return newnode;//没让返回就不用返回
}
void addAtTail(int val) {
LinkedNode *newnode=new LinkedNode(val);
//LinkedNode *cur=dummyHead->next;//需要考虑到空链表的情况
LinkedNode *cur=dummyHead;
while(cur->next!=NULL)
{
cur=cur->next;
}
cur->next=newnode;
size++;
//return newnode;//没让返回就不用返回
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val) {
if(index>size) return;
if(index<0) index=0;
LinkedNode *newnode=new LinkedNode(val);
LinkedNode *cur=dummyHead;//注意这里和上面获取下标的区别,起始提前了一位,后面插入的位置就在循环结束cur位置的后面插入。
while(index!=0)
{
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!=0)
{
index--;
cur=cur->next;
}
//LinkedNode *temp=new LinkedNode(0);这里随便定义一个就好,不用那么复杂
//temp=cur->next;
LinkedNode *temp=cur->next;
cur->next=cur->next->next;
delete temp;
//delete命令指示释放了temp指针原本所指的那部分内存,
//被delete后的指针temp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句temp=nullptr,temp会成为乱指的野指针
//如果之后的程序不小心使用了temp,会指向难以预想的内存空间
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);
*/
四、反转链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre=NULL;
ListNode *cur=head;
ListNode *temp;
while(cur!=NULL)
{
temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
}
return pre;
}
};