链表
1.单链表的定义
1.1结构定义
1. 链表实质上是由一个头部节点引导的,通过结点指针连接在一起的数据结构
2. 所以我们只需创建一个头部节点,通过节点内含有的指针即可将整个单链表给表示出来
3. 于是由定义链表引入到定义链表节点,链表的某一个节点有应该有要存的数据,在此全部为int类型数据,还有一个指针域用来指向该节点的下一个节点,也就是结点指针
//1.链表节点的定义和链表的定义
typedef struct LinkNode{
int data; //一个链表节点存在一个数据和一个指向下一个节点的指针
struct LinkNode *next;
/*
在这里你可能会疑问为什么会是链表节点指针?
这是因为当前节点存放的应该是下一位的地址,所以节点定义时它的下一位应该是一个地址,而只有指针存放的才是地址所以使用了指针;
又因为当前节点的下一位也是一个节点,所以用的是节点指针而不是其他类型的指针
*/
}LinkNode,*LinkList; //最后实际上应该是只有一个变量名的,是给这个自定义的数据结构进行命名操作,但在这里却多了一个
/*
第一个变量名的意义是不变的,任然是为这个数据结构命名,而第二个则是一个缩写 LinkNode* LinkList;
我理解的意思是在栈区开辟了一块空间,并将其命名为LinkList,实际上是表示的应该是链表的头部节点,因为有了头部节点就可以找到这一整个头部节点,所以和链表没差,为了方便就直接命名为链表了,所以在看到LinkList是直接可以将其看作是头部节点head即可。
*/
1.2 初始化链表
1. 上述的结构定义仅仅只是相当于在内存的栈区中声明了一个变量,并没有开辟空间供变量进行操作
2. 所以这一步就是在堆区中开辟内存,而通过上面定义链表时可以知道LinkList实质上是一个头部节点,也就是一个链表节点,所以只要new一个节点指针即可
//2.初始化链表
LinkList init_list(){
LinkList l = new LinkNode;//在这里知道了为什么在定义LinkList时用的是指针了,因为new一个节点返回的是与节点类型相同的地址,所以要用指针来进行接受,如果不理解LinkList l是什么意思,可以将其换成LinkNode * l,说明LinkList就相当于一个结点指针
l->next = NULL;
return l; //因为返回的是一个LinkList所以函数的头部也就是LinkList
}
//3.初始化节点
LinkNode *init_node(int val){
LinkNode *node = new LinkNode; //先开辟空间
node->next = NULL;// 然后对值和指针进行初始化
node->data = val;
return node; //接着返回节点即可,因为node 是LinkNode *,所以函数的头部就是LinkNode *
}
/*
为什么要这样进行初始化操作呢?
在这里只是将创建链表和节点进行了封装,想要创建一个链表你只需调用init_list()即可得到一个空的链表,然后只需声明一个链表的变量进行接受即可,就像这样LinkList l1 = init_list()
而如果想要创建一个节点的话,也仅仅只需调用一个函数即可,LinkNode *q = init_node(3) ,这样就创建了一个值为3的节点,它的下一个指针指向的是空,
因为在后续操作中会频繁遇到创建节点这个操作所以在此就将其抽象成了一个方法,以便后续操作
*/
1.3 清除结点和链表
1.3.1 清除结点
void clear_node(LinkNode *node){
if(!node){
free(node);
}
}
1.3.2 清除链表
void clear_list(LinkList &l){
if(l == NULL) return;
//因为是链表所以要先将与虚拟头结点相连的结点删除
LinkNode *p = l->next;//用于指向真实的头节点
LinkNode *tmp; // 用于记录
while(p){
tmp = p->next;
clear_node(p);
p = p->next;
}
//将与虚拟头结点相连的节点删除后就要删除虚拟头节点了
free(l);
}
1.4 结构操作
1.4.1 插入节点
描述:传入要插入的索引位置和要插入的值,修改传入的链表
1. 重新定义一个概念,我们将上面说的头部节点视为虚拟头节点,实际的头节点应该是虚拟头结点的下一位,这样的话当传入要插入的节点是只需循环传入的index次数即可找到前一个结点
2.想要在一个完整的链表中插入一个节点首先需要找到要插入位置的前一个结点
//插入
int insert_node(LinkList &l,int index,int val){
//1.条件判断
if(index < 0 || index > l->length) return -1;
//2.找到要插入索引位置的前一位
//3.要想找到就得先定义一个结点让其指向虚拟的头节点,然后循环找到前一位
LinkNode *pre = l;
while(index--){
pre = pre->next;// 循环结束后pre所指的就是要插入的前一位,这也是为什么要设虚拟头节点原因
}
//4.构建要插入的结点
LinkNode *new_node = init_node(val);
//5.插入的过程
new_node->next = pre->next;
pre->next = new_node;
// 6.别忘了插入后链表的长度加1
l->length++;
return 0;
}
1.4.2 删除结点
// 4.2 删除结点
int delete_node(LinkList &l,int index){
if(index<0 || index >= l->length) return -1;
if(!l) return -1;
//1.删除结点同样也需要找到要删除的结点的前一个结点
LinkNode *pre = l;
while(index--){
pre = pre->next;
}
//2.用一个结点变量来记录要删除节点的下一个结点
LinkNode *tmp = pre->next->next;
// 3. 清理被删除的结点
clear_node(pre->next);
// 4.找到后直接让pre指向要删除节点的下一个结点即可
pre->next = tmp;
//3.链表长度减1
l->length--;
return 0;
}
1.5 进行展示
void display(LinkList l) {
cout<<endl;
cout<<"链表["<<l->length<<"]:"; //只是为了统一输出的格式而已
for (LinkNode* p = l->next; p; p = p->next) {
cout<< p->data <<"->";
}
cout<<"NULL"<<endl;
}
1.6 主函数测试
#define MAX_OP 30
int main()
{
LinkList l = init_list(); //初始化一个链表
cout<<"start:"<<endl;
for (int i = 0; i < MAX_OP; i++) {
int op = rand() % 4;//生成随机数
int ind = rand() % (l->length + 1);//使随机数的索引生成在[1,length]之间
int val = rand() % 100;
switch (op) {
case 0: {
cout<<"insert "<<val<<" at "<<ind<<" to LinkList";
insert_node(l, ind, val);
}break;
case 1: {
cout<<"insert "<<val<<" at "<<ind<<" to LinkList";
insert_node(l, ind, val);
}break;
case 2: {
cout<<"insert "<<val<<" at "<<ind<<" to LinkList";
insert_node(l, ind, val);
}break;
case 3: {
cout<<"erase item at "<<ind<<" from LinkList";
delete_node(l, ind);
}break;
}
display(l);
cout<<endl;
}
return 0;
}
编译实现:
成功!!!