只是一个鶸用来记录学习内容的文章罢了,如果能多少帮到你那真是太好了,发现错误欢迎指正。
结构体定义:
struct Link_list
{
int data;
struct Link_list* next;
};
typedef Link_list* List;
将数据封装成节点:
数据要想放入链表中必须将数据做成节点,由于很多操作都需要所以单独写成一个函数,这里只拿int类型举例,其他数据类型差别不大
List Create_node(int data)
{
List node = (List)malloc(sizeof(Link_list));
node->data = data;
node->next = NULL;
return node;
}
创建
头插法
设置一个不存放有意义数据的头节点用于头插,后输入的数据排在表前端,头节点不动,每次将生成的新节点插入头节点后。
List Head_create()
{
List head = Create_node(-23456),node;//头插法带头节点,头节点内放无意义的数
int temp;
cout<<"头插法:请输入链表中的数字"<<endl;
cin>>temp;
while(temp!=-1)
{
//生成节点
node = Create_node(temp);
//将新生成节点插在头节点后,第一个数据节点前的位置
node->next = head->next;
head->next = node;
cin>>temp;
}
return head;
}
尾插法
初始化head,tail为NULL,head用于保存第一个节点即表头地址,tail用于保存当前表末最后一个元素的地址,tail在创建过程中会被不断地更新,类似于临时变量。
List Tail_create()
{
List head = NULL,tail = NULL,node;
int temp;
cout<<"尾插法:请输入链表中的数字"<<endl;
cin>>temp;
while(temp != -1)
{
//生成节点
node = Create_node(temp);
//记录表头地址
if(head == NULL) head = node;
//将当前链表的尾结点连接到新生成的结点
if(tail != NULL) tail->next = node;
//更新尾结点为新生成的结点
tail = node;
cin>>temp;
}
return head;
}
修改
插入数据
往链表中插入数据只需知道链表表头地址,待插入数据,以及待插入位置即可,在函数内用前后指针的方式遍历链表,前指针始终在后指针前一步,当遍历到待插入位置时,也就知道了待插入位置前后的节点地址,分别让后指针连接到新节点,新节点连接到前指针就可以了。例外的是当待插入位置就是表头时,则只需要将新生成节点连接到表头并将头节点更新为新生成的节点即可。
void Insert(List* head,int pos,int n)
{
List node = Create_node(n);
List pre = *head,post;//pre为前指针,post为后指针
int index = 0;
if(pos == 0)
{
node->next = *head;
*head = node;
}
else
{
while(pre && index!=pos)
{
post = pre;
pre = pre->next;
index++;//一边遍历链表一边计数下标
}
//发现待插入位置即跳出循环后,将新节点node插在post和pre之间
post->next = node;
node->next = pre;
}
}
删除数据
删除指定位置的节点和插入很相似,也需要知道待删除位置的前后两处节点地址才能完成,同样用前后指针的遍历,直到发现带查找的位置后跳出循环,将前指针指向的节点连接到后指针的下一个节点,即跳过前指针指向的节点,前指针指向的节点就被删除了。同样的,如果待删除的节点就是表头的话只需要将头节点更新为原表的第二个节点,第一个节点失去了与整个链表的连系,也就被删除了,但是需要警惕该链表是不是带无效头结点。
void Delete(List* head,int pos)
{
List pre = *head,post;//pre为前指针,post为后指针
int index = 0;
//如果删除的是头节点,则将头节点改为删除前链表的头节点的下一个节点即可
if(pos == 0)
{
//free(*head);
*head = (*head)->next;
return;
}
while(pre)
{
if(index == pos) break;
post = pre;
pre = pre->next;
index++;
}
//待删除的节点即为pre,让post指向pre的下一个节点,pre在链表中已丢失前后联系即被删除
post->next = pre->next;
}
逆置
链表逆置有很多种思路,可以逆序遍历原表同时将元素去除放在新表中,也可以不使用多余的空间在原表上就地逆置,这里说的是后者。
大致思路就是遍历原表的同时将该节点到后一节点的连接断开,改为连接到前一节点。仍然用前后指针的思想,前指针和后指针同时向前并将前指针指向的节点连接到后指针指向的节点,这样当前指针踏空(即遍历到NULL)的时候后指针就成为了新的头节点。
void Reverse(List* head)
{
//Old记录原表头节点,New记录新表头节点,pre为前驱指针
List Old_head = *head,New_head = NULL,pre;
while(Old_head)
{
//前驱指针向前一步
pre = Old_head->next;
//Old指向New,即原表中后一个节点指向前一个,若Old为原表第一个则会指向NULL,因为New初始化为NULL
Old_head->next = New_head;
//New更新并向前一步
New_head = Old_head;
//Old向前一步
Old_head = pre;
}
*head = New_head;//更改原表的头节点为新的头节点
}
遍历及查询
遍历就是拿到头节点地址后一直将指针移向它的下一个结点直到为空,期间输出节点内的数据
void Print_list(List head)
{
List p = head;
while(p)
{
//if(p->data!=-23456)
cout<<p->data<<" ";
p = p->next;
}
cout<<endl;
}
查询可以分为根据下标求对应元素和根据元素求元素对应的下标,这里拿后者举例,返回链表中第一个与带查询元素相同的节点的下标,很好理解不需要过多解释。
int Find(List head,int number)
{
int index = 0;
while(head)
{
if(head->data == number)
{
return index;
}
head = head->next;
index++;
}
return -1;
}
总结
链表之于数组各有各的优点,链表在大规模删除插入以及未知输入规模时更适合,数组则强在能快速通过下标查询。数组自不必说,链表在很多语言中都已经封装的非常完善,但是通过学习这些底层的数据结构可以更深入理解对内存的操作,知其然&&知其所以然可以走得更远。这也是我写的第一篇文章,以后看到可能会贻笑大方,但凡是贵在坚持,希望能一起进步。