顺序表(整体代码)
#include<iostream>
#include<time.h>
using namespace std;
//顺序表的结构定义
typedef struct SqList
{
int count;//顺序表存储了多少个元素
int size;//顺序表的大小
int* data;//顺序表的存储区
}SqList;
//初始化顺序表
SqList* GetNewList(int n)
{
SqList* p = (SqList*)malloc(sizeof(SqList));//指针大小
p->size = n;
p->count = 0;
p->data = (int*)malloc(sizeof(int) * n);
return p;//返回指向这个顺序表的指针
}
//销毁顺序表
void clear(SqList* L)
{
if (L == NULL)
{
return;
}
free(L->data);
free(L);
return;
}
//扩容操作
int expand(SqList* L)
{
if (L == NULL)
return 0;
cout << "发生了扩容操作" << endl;
//L->data = (int*)realloc(L->data, sizeof(int)*1.5*L->size);//扩容到原来的1.5倍,这种做法可能有bug
int* p = (int*)realloc(L->data, sizeof(int) * 1.5 * L->size);
if (NULL == p)
{
cout << "扩容失败" << endl;
return 0;
}
L->data = p;//规避bug
L->size *= 1.5;
return 1;
}
//插入操作,插入到pos位置上
int insert(SqList* L, int pos, int val)
{
if (pos < 0 || pos > L->size)
{
cout << "插入位置不合法" << endl;
return 0;
}
if (pos > L->count)
{
cout << "插入失败,请按顺序插入" << endl;
return 0;
}
if (L->size == L->count && !expand(L))
{
cout << "存储空间已满,无法插入" << endl;
return 0;
}
for (int i = L->count - 1; i >= pos; i--)
{
L->data[i + 1] = L->data[i];
}
L->data[pos] = val;
L->count += 1;
return 1;
}
//删除操作
int erase(SqList* L, int pos)
{
if (pos<0 || pos>L->count)
{
cout << "删除位置不合法" << endl;
return 0;
}
if (L->count == 0)
{
cout << "存储空间为空,无法删除" << endl;
return 0;
}
for (int i = pos; i < L->count - 1; i++)
{
L->data[i] = L->data[i + 1];
}
L->count -= 1;
return 1;
}
//输出操作
void cout_List(SqList* L)
{
int len = 0; // 用来记录输出的长度,为输出的格式服务
for (int i = 0; i < L->size; i++)
{
len += printf("%3d", i);
}
printf("\n");
for (int i = 0; i < len; i++)
{
printf("-");
}
printf("\n");
for (int i = 0; i < L->count; i++)
{
printf("%3d", L->data[i]);
}
printf("\n");
return;
}
int main()
{
srand(time(0));
SqList* L = GetNewList(2);
for (int i = 0; i < 10; i++)
{
int op = rand() % 5;//定义一个随机数来决定是插入还是删除
int val, pos = 0;
switch (op)
{
case 0:
case 1:
case 2:
case 3:
pos = rand() % (L->count + 2);//+2是为了触发非法操作
val = rand() % 100;
cout << "插入" << val << "到" << pos << "位置" << endl;
insert(L, pos, val);
break;
case 4:
pos = rand() % (L->count + 2);
cout << "删除" << pos << "位置的数据" << endl;
erase(L, pos);
break;
}
cout_List(L);
}
clear(L);
return 0;
}
运行结果(不唯一)
删除1位置的数据
删除位置不合法
0 1
------
插入59到0位置
0 1
------
59
删除0位置的数据
0 1
------
插入24到0位置
0 1
------
24
插入22到1位置
0 1
------
24 22
插入95到3位置
插入位置不合法
0 1
------
24 22
插入11到2位置
发生了扩容操作
0 1 2
---------
24 22 11
插入17到0位置
发生了扩容操作
0 1 2 3
------------
17 24 22 11
插入32到2位置
发生了扩容操作
0 1 2 3 4 5
------------------
17 24 32 22 11
插入53到5位置
0 1 2 3 4 5
------------------
17 24 32 22 11 53
插入操作:
//插入操作,插入到pos位置上
int insert(SqList* L, int pos, int val)
{
if (pos < 0 || pos > L->size)
{
cout << "插入位置不合法" << endl;
return 0;
}
if (pos > L->count)
{
cout << "插入失败,请按顺序插入" << endl;
return 0;
}
if (L->size == L->count && !expand(L))
{
cout << "存储空间已满,无法插入" << endl;
return 0;
}
for (int i = L->count - 1; i >= pos; i--)
{
L->data[i + 1] = L->data[i];
}
L->data[pos] = val;
L->count += 1;
return 1;
}
删除操作:
//删除操作,删除pos位置上的数据
int erase(SqList* L, int pos)
{
if (pos<0 || pos>L->count)
{
cout << "删除位置不合法" << endl;
return 0;
}
if (L->count == 0)
{
cout << "存储空间为空,无法删除" << endl;
return 0;
}
for (int i = pos; i < L->count - 1; i++)
{
L->data[i] = L->data[i + 1];
}
L->count -= 1;
return 1;
}
扩容操作:
realloc工作原理:
realloc会试着原内存地址的基础上向后进行延展,打个比方:原来的内存大小是4个字节,由于这4个字节大小的内存在整个内存空间中只是一小段,那它后面可能有没被使用的空间,此时realloc的尝试就成功了,那么realloc的返回值与原地址是一样的;
如果后面没有足够的空间进行扩容,那么realloc会重新找一片新的足够扩容的空间,将原来空间中的内容拷贝到新空间里来,再将原空间销毁并返回新空间的地址。
又如果找不到足够用来扩容的空间,那么realloc就会返回NULL,并且原内存不动,此时会出现bug。
//扩容操作
int expand(SqList* L)
{
if (L == NULL)
return 0;
cout << "发生了扩容操作" << endl;
//L->data = (int*)realloc(L->data, sizeof(int)*1.5*L->size);//扩容到原来的1.5倍,这种做法可能有bug
int* p = (int*)realloc(L->data, sizeof(int) * 1.5 * L->size);
if (NULL == p)
{
cout << "扩容失败" << endl;
return 0;
}
L->data = p;//规避bug
L->size *= 1.5;
return 1;
}
链表(整体代码)
#include<iostream>
#include<time.h>
using namespace std;
//无头链表
//定义链表节点信息
typedef struct ListNode
{
int data;//数据域
struct ListNode* next;//指针域
}ListNode;
//初始化链表节点信息
ListNode* GetListNode(int val)
{
ListNode* p = (ListNode*)malloc(sizeof(ListNode));
p->data = val;
p->next = NULL;
return p;
}
//销毁链表节点
void clear(ListNode* head)
{
if (NULL == head) return;
ListNode* q = NULL;//定义一个临时节点
for (ListNode* p = head; p; p = q)
{
q = p->next;//如果不将p的下一个节点信息传给q而直接free的话会造成内存泄露
free(p);
}
}
//插入操作,插入pos前一位并返回新插入链表节点的首地址
ListNode* insert(ListNode* head, int pos, int val)
{
int len = 0;
for (ListNode* p = head; p; p = p->next)
{
len += 1;
}
if (len < pos) pos = len;//超出链表长度则插入最后一个位置
ListNode* node = GetListNode(val);
ListNode new_head, * p = &new_head;//定义一个虚拟头节点,并将这个虚拟头节点的地址传给指针p
new_head.next = head;
/*
定义一个虚拟头节点有个好处:
如果待插入位置是1,p就向后走1个位置...
这样的话,逻辑思路就更加清楚
*/
for (int i = 0; i < pos; i++)
{
p = p->next;
}
node->next = p->next;
p->next = node;
return new_head.next;
}
//删除操作,删除pos位置的值
ListNode* erase(ListNode* head, int pos)
{
if (pos < 0)
{
cout << "删除位置不合法" << endl;
return head;
}
if (head == NULL)
{
cout << "无节点可删除" << endl;
return head;
}
if (pos == 0)
{
ListNode* new_head = head->next; // 新的头节点
free(head); // 释放原头节点
return new_head;
}
ListNode* current = head;
int i = 0;
while (i < pos - 1 && current->next != NULL)
{
current = current->next;
i++;
}
if (i < pos - 1 || current->next == NULL)
{
cout << "删除位置不合法" << endl;
return head;
}
ListNode* temp = current->next;
current->next = temp->next;
free(temp);
return head;
}
//打印操作
void cout_list(ListNode* head)
{
int len = 0;
for (ListNode* p = head; p; p = p->next)
{
len += 1;
}
for (int i = 0; i < len; i++)
{
printf("%-4d", i);
cout << " ";
}
cout << endl;
for (ListNode* p = head; p; p = p->next)
{
printf("%-3d", p->data);
if (p->next != NULL)
{
cout << "->";
}
}
cout << endl;
}
//查找操作
int find(ListNode* head, int val)
{
ListNode* p = head;
int n = 0;//记录经历了多少个节点
while (p)
{
if (p->data == val)
{
cout_list(head);
int len = n * (3 + 2);//3是一个数据占3位,2是'->'的长度,len代表空格的数量
for (int i = 0; i < len; i++)
{
cout << " ";
}
cout << "^" << endl;
for (int i = 0; i < len; i++)
{
cout << " ";
}
cout << "|" << endl;
return 1;
}
n += 1;
p = p->next;
}
return 0;
}
int main()
{
srand(time(0));
ListNode* head = NULL;
for (int i = 0; i < 10; i++)
{
int op = rand() % 5;//定义一个随机数来决定是插入还是删除
int val, pos = 0;
switch (op)
{
case 0:
case 1:
case 2:
case 3:
pos = rand() % (i + 1);
val = rand() % 100;
cout << "插入" << val << "到" << pos << "位置" << endl;
head = insert(head, pos, val);
break;
case 4:
pos = rand() % (i + 1);
cout << "删除" << pos << "位置的数据" << endl;
head = erase(head, pos);
break;
}
cout_list(head);
}
int n = 0;
while (cin >> n && n >= 0)
{
if (!find(head, n))
{
cout<<"查找失败"<<endl;
}
}
clear(head);
return 0;
}
插入操作:
(无头节点的链表)
//插入操作,插入pos前一位并返回新插入链表节点的首地址
ListNode* insert(ListNode* head, int pos, int val)
{
int len = 0;
for (ListNode* p = head; p; p = p->next)
{
len += 1;
}
if (len < pos) pos = len;//超出链表长度则插入最后一个位置
//防止第一个节点改变原来的链表地址
if (pos == 0)
{
ListNode* p = GetListNode(val);
p->next = head;
return p;
}
ListNode* p = head;
for (int i = 0; i < pos-1; i++)
{
p = p->next;
}
ListNode* new_node = GetListNode(val);
//以下两步不能写反,否则会丢失后续链表的信息,造成内存泄露
new_node->next = p->next;
p->next = new_node;
return head;
}
(有头节点的链表)
//插入操作,插入pos前一位并返回新插入链表节点的首地址
ListNode* insert(ListNode* head, int pos, int val)
{
int len = 0;
for (ListNode* p = head; p; p = p->next)
{
len += 1;
}
if (len < pos) pos = len;//超出链表长度则插入最后一个位置
ListNode* node = GetListNode(val);
ListNode new_head, * p = &new_head;//定义一个虚拟头节点,并将这个虚拟头节点的地址传给指针p
new_head.next = head;
/*
定义一个虚拟头节点有个好处:
如果待插入位置是1,p就向后走1个位置...
这样的话,逻辑思路就更加清楚
*/
for (int i = 0; i < pos; i++)
{
p = p->next;
}
node->next = p->next;
p->next = node;
return new_head.next;
}
删除操作:
//删除操作,删除pos位置的值
ListNode* erase(ListNode* head, int pos)
{
if (pos < 0)
{
cout << "删除位置不合法" << endl;
return head;
}
if (head == NULL)
{
cout << "无节点可删除" << endl;
return head;
}
if (pos == 0)
{
ListNode* new_head = head->next; // 新的头节点
free(head); // 释放原头节点
return new_head;
}
ListNode* current = head;
int i = 0;
while (i < pos - 1 && current->next != NULL)
{
current = current->next;
i++;
}
if (i < pos - 1 || current->next == NULL)
{
cout << "删除位置不合法" << endl;
return head;
}
ListNode* temp = current->next;
current->next = temp->next;
free(temp);
return head;
}
查找操作:
//查找操作
int find(ListNode* head, int val)
{
ListNode* p = head;
int n = 0;//记录经历了多少个节点
while (p)
{
if (p->data == val)
{
cout_list(head);
int len = n * (3 + 2);//3是一个数据占3位,2是'->'的长度,len代表空格的数量
for (int i = 0; i < len; i++)
{
cout << " ";
}
cout << "^" << endl;
for (int i = 0; i < len; i++)
{
cout << " ";
}
cout << "|" << endl;
return 1;
}
n += 1;
p = p->next;
}
return 0;
}
小练习
反转链表(Leetcode-206):
给你单链表的头节点head,请你反转链表,并返回反转后的链表。
(非递归做法)
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution
{
public:
ListNode* reverseList(ListNode* head)
{
ListNode* p = head;
ListNode new_head, * q;//定义虚拟头节点
new_head.next = NULL;
//采用头插法
while (p)
{
q = p->next;//p就相当于一个标记
p->next = new_head.next;
new_head.next = p;
p = q;
}
return new_head.next;
}
};
(递归做法)
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution
{
public:
ListNode* reverseList(ListNode* head)
{
//原链表为空或者只有一个节点就不用处理
if (head == NULL || head->next == NULL)
{
return head;
}
//对于头节点以外部分的链表,反转后的尾节点就是头节点的下一个节点
ListNode* tail = head->next;
//处理头节点以外部分的链表,new_head就是反转链表的地址
ListNode* new_head = reverseList(head->next);
//处理头节点,尾插法
head->next = tail->next;
tail->next = head;
return new_head;
}
};
环形链表(Leetcode-141):
给你一个链表的头节点 head
,判断链表中是否有环。
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution
{
public:
/*
* 就像操场上跑圈,跑得快的一定会追上跑得慢的,除非没有环
*/
bool hasCycle(ListNode* head)
{
//快慢指针
ListNode* p = head, * q = head;
while (q && q->next)
{
p = p->next;//p每次跑一步
q = q->next;//q每次跑两步
if (p == q)
return true;
}
return false;
}
};
快乐数(Leetcode-202):
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
这个变化过程就像是链表:
如果把1看作空地址NULL,那么问题就变成了:从链表的一个节点开始,究竟能不能到达空地址。这就和环形链表很像了:如果它不是快乐数,那么它就会形成一个环,无法到达空地址。所以采用“快慢指针”的做法。
class Solution
{
public:
int getnext(int x)
{
//y代表变化后的数字
int d, y = 0;
while (x)
{
d = x % 10;//d代表个位数
y += d * d;
x /= 10;
}
return y;
}
bool isHappy(int n)
{
int p = n, q = n;
while (p != 1)
{
p = getnext(p);
q = getnext(getnext(q));
//形成了‘环’且没到达终点
if (p == q && p != 1)
return false;
}
return true;
}
};
旋转链表(Leetcode-61):
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
struct ListNode
{
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution
{
public:
int getLength(ListNode* head)
{
int n = 0;
while (head)
{
n += 1;
head = head->next;
}
return n;
}
ListNode* rotateRight(ListNode* head, int k)
{
//由于k可能大于链表的长度,故对k预处理
if (head == NULL) return head;
int len = getLength(head);
k %= len;
if (k == 0) return head;
/*
这道题的解题思路就是将倒数k个节点分割出来放到‘前面’去,那么如何确定分割点呢?
设定两个指针p、q,q往后移k+1个位置(即p和q间隔k个位置),然后让q和p同时向后移动,当q=NULL时,
p所在的位置就是分割点了。
*/
ListNode* q = head, * p = head;
for (int i = 0; i <= k; i++)
{
q = q->next;
}
while (q)
{
p = p->next;
q = q->next;
}
//分割
q = p->next;
p->next = NULL;
//分割完成,此时q是第二部分链表的头指针,接下来要让第二部分链表的尾节点接上原链表的头节点
p = q;//这句代码执行后,p也变成了第二部分链表的头指针了
//拼接,这个循环执行完之后,p就指向了第二部分链表的尾节点
while (p->next)
{
p = p->next;
}
p->next = head;
return q;
}
};
删除链表倒数第N个节点(Leetcode-19):
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
struct ListNode
{
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
class Solution
{
public:
/*这道题与上道题的思路很想:要想删除第n个节点,就要找到第倒数第n+1个节点
* 如何找呢?
* 双指针+等距移动法
*/
ListNode* removeNthFromEnd(ListNode* head, int n)
{
//如果只有5个节点,又恰好是删除倒数第5个位置的节点,那么N+1就不存在,为了避免这种情况,最好引入一个虚拟头节点
ListNode new_head, * p = &new_head, * q = p;
new_head.next = head;
//让q指针往后移动n+1位
for (int i = 0; i <= n; i++)
{
q = q->next;
}
//让p指向待删除节点的前一位
while (q)
{
p = p->next;
q = q->next;
}
//删除节点
p->next = p->next->next;
return new_head.next;
}
};
环形链表Ⅱ(Leetcode-142):
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
/*环形链表Ⅱ的数学思路拆解:
*初始化一个前提:快指针每次前进2步,满指针每次前进1步。
*假设慢指针走了a的距离到达环的入口,
那么快指针就走了2a的距离,
设环的长度为x(也就是整个链表的长度减去a的长度),
那么快指针在环中经过的距离就是a,
于是快指针走完整个环的剩余长度就是x-a,
由于每一次‘行动’快指针都比慢指针多走1步,
那么两指针如果想在环中相遇,那么需要‘行动’x-a次,
此时二者相遇的位置沿环方向距离环入口的长度为a,
巧妙的是:链表的头节点距离环入口的距离也是a
*此时,如果把快慢指针中的一个扔回头节点,
再让它们以相同的速度向后移动,
那么它们再次相交的位置就是环入口。
*/
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution
{
public:
ListNode* detectCycle(ListNode* head)
{
ListNode* p = head, * q = head;
while (q && q->next)
{
p = p->next;
q = q->next->next;
if (p == q)
break;
}
//循环结束有两种情况:①链表没有环②二者相遇了
//排除第一种
if (q == NULL || q->next == NULL)
{
return NULL;
}
p = head;//把p扔回头节点
while (p != q)
{
p = p->next;
q = q->next;
}
return q;
}
};
反转链表Ⅱ(Leetcode-92):
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
/*
* 原链表由于左区间不为1,规模减小为子1;当左区间为1,右区间不为1,缩小规模为子2再到子3。
* 此时到达边界条件
*/
struct ListNode
{
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
class Solution
{
public:
ListNode* reverseBetween(ListNode* head, int left, int right)
{
//边界条件
if (left == 1 && right == 1)
{
return head;
}
//左区间不为1
if (left != 1)
{
head->next = reverseBetween(head->next, left - 1, right - 1);
}
//左区间为1,右区间不为1,此时也说明当前链表的头节点在反转区域内
else
{
//记录反转完之后的尾节点
ListNode* tail = head->next;
//对剩下的部分进行反转
ListNode* new_head = reverseBetween(head->next, left, right - 1);
//将当前链表的头节点插入到记录的尾节点之后
head->next = tail->next;
tail->next = head;
head = new_head;
}
return head;
}
};