一、链表
1.链表实现以及在头部插入结点
先来一段代码....
该代码包含创建链表并在头部插入结点,遍历链表并打印结点数据,接下来逐步分析,简单的基础语法不过多记录....
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* head;
void Insert(int x) {
Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = NULL;
head = temp;
}
void Print() {
struct Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
int main(void)
{
head = NULL;
int n;
printf("How many numbers?\n");
scanf("%d", &n);//链表的数量
int x;//用户插入的数据
for (int i = 0; i < n; i++) {
printf("Enter the number\n");
scanf("%d", &x);
Insert(x);
Print();
}
return 0;
}
a.创建链表
创建一个链表需要创建结点类型(一个结构体类型,包含值域和指针域),一个头节点,用头结点地址来找到整个链表,头结点指向空地址
struct Node {
int data;
struct Node* next;//指向Node结构体类的指针(指向下一个结点)
};
struct Node* head;//全局变量,便于函数直接引用
int main(void){
head = NULL;//还没有结点,头结点先指向空地址
}
b.链表结点插入函数
依据思路,链表为空时我们可以想到这样的代码
void Insert(int x) {
Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = NULL;
head = temp;
}
但这只能满足链表为空的情况,因此加入if判断一下情况
void Insert(int x) {
Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = NULL;
if(head != NULL) temp->next = head;
head = temp;
}
最终代码可以简化为这样,因为包含了链表为空的情况
void Insert(int x) {
Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = NULL;
head = temp;
}
如果改变一下指针指向的顺序,先让头结点指向新结点的地址,这样就会失去原来头结点指向的地址,导致整个链表断开。
c.链表遍历打印数值的函数
void Insert(int x) {
Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = NULL;
head = temp;
}
d.拓展思考
如果head不是全局变量
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* next;
};
//struct Node* head; 注释掉了
Node* Insert(Node* head ,int x) { //注意多传了一个参,并且返回类型是指向Node类型的指针
struct Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = head;
head = temp;
return head;
}
void Print(Node* head) { //需要传参
//struct Node* temp = head; 注释掉,有head形参不用temp,因为不会让main函数中head指向的地址丢失
printf("List is:");
while (head != NULL) {
printf("%d", head->data);
head = head->next;
}
printf("\n");
}//注意temp全换成了head
int main(void)
{
Node* head = NULL;//在main函数里创建头结点
int n;
printf("How many numbers?\n");
scanf("%d", &n);
int x;
for (int i = 0; i < n; i++) {
printf("Enter the number\n");
scanf("%d", &x);
head = Insert(head,x);
Print(head);//传参
}
return 0;
}
续上,关于Insert函数还有一种写法
void Insert(Node** head ,int x) { //返回类型为void,传指针的指针
struct Node* temp = (Node*)malloc(sizeof(struct Node));
temp->data = x;
temp->next = *head;
*head = temp;
}
int main(void){
Insert(&head,x);
}
2.任意位置插入结点
先来看一段代码,这段代码实现了在所给定位置插入结点,重点来看Insert函数
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* head;
void Print() {
struct Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
//n是插入位置,data是插入数据
void Insert(int data, int n) {
Node* temp1 = new Node();//这里采用new在堆上开辟内存
temp1->data = data;
temp1->next = NULL;
if (n == 1) {
temp1->next = head;
head = temp1;
return;
}
Node* temp2 = head;
for (int i = 0; i < n - 2; i++) {
temp2 = temp2->next;
}
temp1->next = temp2->next;
temp2->next = temp1;
}
int main(void)
{
head = NULL;
Insert(2, 1);
Insert(3, 2);
Insert(4, 1);
Insert(5, 2);
Print();
return 0;
}
这段代码实现的重点是Insert函数
void Insert(int data, int n) {
Node* temp1 = new Node();//这里采用new在堆上开辟内存
temp1->data = data;
temp1->next = NULL;
if (n == 1) {
temp1->next = head;
head = temp1;
return;//跳出函数
}
Node* temp2 = head;
for (int i = 0; i < n - 2; i++) {//可以思考一下为什么是n-2
temp2 = temp2->next;
}
temp1->next = temp2->next;
temp2->next = temp1;//很多人这块理解不了,实际上temp1、temp2都是指针,对其解引用修改的都是堆上的结点
}
3.任意位置删除一个结点
先来看一段代码,该代码尝试在链表末尾插入,并且可以指定位置删除结点。
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* head;
void Insert(int data) {
Node* temp1 = (Node*)malloc(sizeof(struct Node));
temp1->data = data;
Node* temp2 = head;
if (head == NULL) {
head = temp1;
temp1->next = NULL;
return;
}
while (temp2->next != NULL) {
temp2 = temp2->next;
}
temp2->next = temp1;
temp1->next = NULL;
}
void Print() {
struct Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
void Delete(int n) {
Node* temp1 = head;
if (n == 1) {
head = temp1->next;
free(temp1);
return;
}
for (int i = 0; i < n - 2; i++) {
temp1 = temp1->next;
}
Node* temp2 = temp1->next;
temp1->next = temp2->next;
free(temp2);//释放malloc开辟出的无用数据内存,如果是c++用 delete temp2
}
int main(void)
{
head = NULL;
Insert(1);
Insert(2);
Insert(3);
Insert(4);
int n;
scanf("%d", &n);
Delete(n);
Print();
return 0;
}
a.在链表末尾插入结点
void Insert(int data) {
Node* temp1 = (Node*)malloc(sizeof(struct Node));
temp1->data = data;
Node* temp2 = head;
if (head == NULL) { //特判一下链表为空
head = temp1;
temp1->next = NULL;
return;
}
while (temp2->next != NULL) {
temp2 = temp2->next;
}
temp2->next = temp1;
temp1->next = NULL;
}
总感觉这次写复杂了一点,太晚了,想起来再改进吧...
还有这里体现了链表相比于数组的一个缺点,比如这里在末尾插入结点时需要遍历到链表末尾,而数组则直接使用下标(指针)就可以了
,毕竟数组是一段连续的内存块,任意位置插入也是如此,总感觉讲错了,水平有限,这里就不展开了...
b.任意位置删除结点
void Delete(int n) {
Node* temp1 = head;
if (n == 1) {
head = temp1->next;
free(temp1);
return;
}
for (int i = 0; i < n - 2; i++) {
temp1 = temp1->next;
}
Node* temp2 = temp1->next;
temp1->next = temp2->next;
free(temp2);//释放malloc开辟出的无用数据内存,如果是c++用 delete temp2
}
解释一下for循环吧,初始化i=0时只要n>=2,temp1就可以指向第n-1个结点,这时只需要特判一下n=1的情况,第一个结点没有前一项,应此特判一下,觉得难理解可以自己画一下图,或者等作者补图^_^
4.循环迭代反转链表(只是改变连接)
使用以前的代码,加入了Reverse函数来反转链表,以前提过的就不重复了,直接看Reverse函数的实现
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* head;
void Print() {
struct Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
//n是插入位置,data是插入数据
void Insert(int data, int n) {
Node* temp1 = new Node();//这里采用new在堆上开辟内存
temp1->data = data;
temp1->next = NULL;
if (n == 1) {
temp1->next = head;
head = temp1;
return;
}
Node* temp2 = head;
for (int i = 0; i < n - 2; i++) {
temp2 = temp2->next;
}
temp1->next = temp2->next;
temp2->next = temp1;
}
void Reverse() {
struct Node* current, * prev, * next;
current = head;
prev = NULL;
while (current != NULL) {
next = current->next;
current -> next = prev;
prev = current;
current = next;
}
head = prev;
}
int main(void)
{
head = NULL;
Insert(2, 1);
Insert(3, 2);
Insert(4, 1);
Insert(5, 2);
Print();
Reverse();
Print();
return 0;
}
a.反转链表
void Reverse() {
struct Node* current, * prev, * next;
current = head;
prev = NULL;
while (current != NULL) {
next = current->next;
current = prev->next;
prev = current;
current = next;
}
head = prev;
}
定义了三个指针变量current,prev,next
正如其名,current用于存储当前结点地址,prev用于存储上个结点位置的地址,next用于存储下一个结点位置的地址
之所以建立这三个变量是为了防止链表断裂导致找不到上个位置或下个位置的地址
这个函数也包含了特殊情况,可以自行多尝试几个案例
5.递归方式实现链表反转(打印数值反转)
注意这次只是打印数值时反着打,实际并没有改变链表的结构
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* head = NULL;
void Insert(int data, int n) {
Node* temp1 = new Node();
temp1->data = data;
temp1->next = NULL;
if (n == 1) {
temp1->next = head;
head = temp1;
return;
}
Node* temp2 = head;
for (int i = 0; i < n - 2; i++) {
temp2 = temp2->next;
}
temp1->next = temp2->next;
temp2->next = temp1;
}
void ReversePrint(Node* temp) {
if (temp == NULL) {
printf("\n");
return;
}
ReversePrint(temp->next);
printf("%d", temp->data);
}
void Print() {
struct Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
int main(void) {
Insert(1, 1);
Insert(2, 2);
Insert(3, 3);
Insert(4, 4);
Print();
ReversePrint(head);
return 0;
}
a.递归反向打印
void ReversePrint(Node* temp) {
if (temp == NULL) {
printf("\n");
return;
}
ReversePrint(temp->next);//注意先后位置
printf("%d", temp->data);
}
b.递归正向打印
void ReversePrint(Node* temp) {
if (temp == NULL) {
printf("\n");
return;
}
printf("%d", temp->data);
ReversePrint(temp->next);
}
在递归中位置不同会影响打印的先后顺序,注意递归结束条件的书写
7.关于双向链表
双向链表无需反转,但占用内存更多
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
Node* prev;
Node* next;
};
struct Node* head;
struct Node* CreatNode() {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
void InsertAtHead(int x) {
Node* temp = CreatNode();
temp->data = x;
if (head == NULL) {
head = temp;
return;
}
head->prev = temp;//极容易漏
temp->next = head;
head = temp;
}
void Print() {
Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
void ReversePrint() {
Node* temp = head;
if (head == NULL) return;
printf("List is:");
while (temp->next != NULL) {
temp = temp->next;
}
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->prev;
}
printf("\n");
}
int main(void)
{
head = NULL;
InsertAtHead(1);
InsertAtHead(2);
InsertAtHead(3);
Print();
ReversePrint();
return 0;
}
a.创建双向链表
创建双向链表与单向链表不同在于定义结构体时多了一个指针域用于存储上一个结点的地址
struct Node {
int data;
Node* prev;
Node* next;
};
struct Node* head;
struct Node* CreatNode() {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
为图方便这块头结点使用全局变量,定义CreatNode函数可以在接下来减少重复代码
b.在头部插入结点
void InsertAtHead(int x) {
Node* temp = CreatNode();
temp->data = x;
if (head == NULL) {
head = temp;
return;
}
head->prev = temp;//极容易漏
temp->next = head;
head = temp;
}
感觉相比单向链表就多注意prev指向前一个结点,其他插入也类似,插入部分不过多赘述了
c.打印链表
正向打印
void Print() {
Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
反向打印
void Print() {
Node* temp = head;
printf("List is:");
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->next;
}
printf("\n");
}
二、栈
栈简而言之就是先进后出,后进先出,相比于链表要简单不少
1.用数组实现一个栈
下面的代码用数组实现一个栈,包括进栈以及出栈和打印栈的功能,较为简单,不过多赘述
#include<stdio.h>
#define Maxsize 101
int stack[Maxsize];
int top = -1;//使用全局变量避免传参
void Push(int x) {
if (top == Maxsize - 1) {
printf("stack overflow\n");
return;
}
stack[++top] = x;
}
void Pop() {
if (top == -1) {
printf("stack is empty\n");
return;
}
top--;
}
int Top() {
return stack[top];
}
void Print() {
for (int i = 0; i <= top; i++) {
printf("%d", stack[i]);
}
printf("\n");
}
int main(void)
{
Push(1);
Push(2);
Push(3);
Push(4);
Print();
Pop();
Pop();
Print();
return 0;
}
a.进栈代码
void Push(int x) {
if (top == Maxsize - 1) {
printf("stack overflow\n");
return;
}//这里可以改成创建一个更大的数组并把值拷贝进去,我记得是用memset实现
stack[++top] = x;
}
b.出栈代码
void Pop() {
if (top == -1) {
printf("stack is empty\n");
return;
}
top--;
}
c.打印栈
void Print() {
for (int i = 0; i <= top; i++) {
printf("%d", stack[i]);
}
printf("\n");
}
2.用链表实现一个栈
只对单向链表进行头结点插入基本就可以实现一个栈
#include<stdio.h>
#include<stdlib.h>
struct Node {
int data;
struct Node* link;
};
struct Node* top = NULL;
void Push(int x) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = x;
temp->link = top;
top = temp;
}
void Pop() {
Node* temp;
if (top == NULL) return;
temp = top;
top = top->link;
free(temp);
}
void Print(){
if (top == NULL) {
printf("stack is empty");
return;
}
Node* temp = top;
while (temp != NULL) {
printf("%d", temp->data);
temp = temp->link;
}
printf("\n");
}
int main() {
Push(1);
Push(2);
Push(3);
Print();
Pop();
Print();
return 0;
}
a.栈的实现
struct Node {
int data;
struct Node* link;
};
struct Node* top = NULL;
b.入栈
void Push(int x) {
Node* temp = (Node*)malloc(sizeof(Node));
temp->data = x;
temp->link = top;
top = temp;
}
c.出栈
void Pop() {
Node* temp;
if (top == NULL) return;
temp = top;
top = top->link;
free(temp);//别忘了释放无用数据的内存
}