口诀:插入先连后再前,删除留前连后后;
(插入的时候需要先连后面,把原本的下一个cur->next给newnode->next,再更新cur->next为newnode; 删除的时候cur是删除位置index的前一个,删的是cur->next,这样才能把后后的cur->next->next连到cur上,最后delete完手动nullptr一下指针。)
**遍历时得由一个新指针 cur,不然head遍历完找不回头了。
**
**if不同条件是需要else,否则可能前一个If执行出来后又满足后一个If的条件;小心两个分类中交叉的边界条件 n=0 n =_size-1 **
链表节点定义
//节点数据可以是多种类型,这里设置成int
typedef int datatype;
struct ListedNode{
datatype val;
struct ListedNode* next;//指针变量,放的是地址
ListedNode(int x):val(x), nextnext(nullptr){}
};//用来定义新结点
一、单链表
1、打印
在遍历链表的时候需要一个新指针来遍历,不用头指针是因为如果用head,遍历完成之后head指向最后一个了,就找不着这个链表了,所以需要一个新指针
void printLinkedList(){
LinkedNode* cur = _dummyhead->next;
while(cur != nullptr){
cout<< cur->val<<"->";
cur = cur->next;
}
cout<<endl;
}
2、插入
(1)插尾
//插尾
void addAtTail(int val) {
LinkedNode* newnode = new LinkedNode(val);
LinkedNode* cur = _dummyhead;
while(cur->next){
cur= cur->next;
}
cur->next = newnode;
_size++;
}
}
理解:newnode是指向新结点的指针变量,他存放的内容是地址,和指向新结点的cur一样的地址
(2)插头
插入时,先连接后,再连接前,用到了长度,添加节点长度也发送变化
//插头
//**插入时,先连接后,再连接前
void addAtHead(int val) {
LinkedNode* newnode = new LinkedNode(val);
newnode->next = _dummyhead->next;
_dummyhead->next = newnode;
//**用到了长度,添加节点长度也发送变化
_size++;
}
(3)第n个位置
3、删除
时间复杂度: O(n)
空间复杂度: O(1)
需要删除掉delete这个结点,
如果有指针指向删除掉的节点,该指针也要=NULL否则就是悬空指针(指向的地址内存不在了)
前提是head非空,由于删头和删中间不同(删头需要更新并返回头结点),所以构造虚拟头结点,不需要考虑空,头或尾,用完删除虚拟头结点即可。
1、删头,删非头
ListNode* removeElement(ListNode* head, int val) {
while(head != NULL && head->val == val){//删头 小心是while
ListNode* cur = head;
head = head->next;
delete cur;
}
//删非头
ListNode* cur = head;
whille(cur! = NULL && cur->next != NULL){ //**头结点非空
if(head->next->val == val){
//**必须先搞个指针暂放,不然先连接前后,要删的cur-next就找不到了
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;//此时无需更新next因为已经变成新的了
}
else{
cur = cur->next;
}
}
return head;
}
2、虚拟头结点
通用版,设置虚拟头节点
ListNode* removeElement(ListNode* head, int val) {
ListNode* dummyhead = new ListNode(0);//虚拟头结点
dummyhead->next = head;
ListNode* cur = dummyhead;
while(cur->next != NULL){
//由于删除一个是前后两个相连,所以cur保留,看next空不空
if(cur->next->val == val){
ListNode* tmp =cur->next;//要删的
cur->next = cur->next->next;
delete tmp;//删除指针以及指针指向的结点
}
}
//每一个都检查到了,删除虚拟头结点
head = dummyhead->next;//新头结点的地址
delete dummyhead;
return head;
}
获取第n个节点(查找)
小心边界,看是n是从0 开始还是从1开始
为什么是_
_size 作为变量名时,前面的下划线 _ 是一种常见的命名约定,用于表示该变量是一个类的成员变量
class MyLinkedList {
public:
//定义
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int x) : val(x), next(nullptr){}
};
//初始化
MyLinkedList() {
_dummyhead = new LinkedNode(0);
_size = 0;
}
//获取节点值
int get(int index) {
//下标0~size-1,无效返回-1
if(index < 0 || index >_size-1){
return -1;
}
//找到第n个,从前往后n~第0个
LinkedNode* cur = _dummyhead->next;//所以这里是原本第0个
while(index--){
cur = cur->next;
}
//第0个不进入后移循环,所以
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){
addAtTail(val);
}
//**这里index>=0,如果有小于0就得再来一个if 重给 index = 0
else if(index >=0 && index < _size){
LinkedNode* newnode = new LinkedNode(val);
LinkedNode* cur = _dummyhead;
while(index--){
cur = cur->next;
}
//要插入的是cur->next
newnode->next = cur->next;//**还没给呐,
cur->next = newnode;
_size++;
}
}
//删除第N个,得找到前一个(删的是cur->next)
void deleteAtIndex(int index) {
//下标有效才删除
if(index <0 || index >_size-1) return;
LinkedNode* cur = _dummyhead;
//**得保留前一个
while(index--){
cur = cur->next; //**此时cur是index前面一个
}
LinkedNode* tmp = cur->next;
cur->next = cur-> next->next;
delete tmp;//释放了tmp内容,但是非空,值随机
tmp = nullptr;//空指针
//**size变了
_size--;
}
void printLinkedList(){
LinkedNode* cur = _dummyhead->next;
while(cur != nullptr){
cout<< cur->val<<"->";
cur = cur->next;
}
cout<<endl;
}
private:
int _size;
LinkedNode* _dummyhead; //只可以在类内部被访问
};