链表
算法:
通俗定义:
解题的 方法 和 步骤。
狭义的定义:
对存储数据的操作。
广义的定义:
泛型
泛型: 无论 数据是 如何存储,我们对该数据的操作都是一样的。
我们至少可以通过 两种 数据结构 来存储数据
数组
优点:
存取速度快
缺点:
需要一个连续很大的内存
插入和删除元素的效率很低
链表
优点:
插入元素效率高
不要一个连续很大的内存
缺点:
查找某个位置的元素 效率低
数据结构:
如何将 现实中 复杂的问题,转化为 计算机中的数据 进行存储。
链表专业术语:
/*
头结点: 头结点的数据类型 和 首节点的数据类型 是一模一样,
头结点 是首节点前面,
头结点 并不存放有需要数据,
设置头结点的目的: 方便 对 链表的操作
头指针: 存放头结点 地址的 指针变量。
首节点: 存放第一个有效数据的节点
尾节点: 存放最后一个有效数据的节点
可以说:首节点是车厢,头结点就是牵引车厢的火车头,头指针就是贴在火车头上的编码
*/
/*
头结点: 头结点的数据类型 和 首节点的数据类型 是一模一样,
头结点 是首节点前面,
头结点 并不存放有需要数据,
设置头结点的目的: 方便 对 链表的操作
头指针: 存放头结点 地址的 指针变量。
首节点: 存放第一个有效数据的节点
尾节点: 存放最后一个有效数据的节点
可以说:首节点是车厢,头结点就是牵引车厢的火车头,头指针就是贴在火车头上的编码
*/
// 定义了一个链表节点的数据类型
struct Node{
int data;
struct Node * pNext;
};
int main(void){
return 0;
}
确定一个链表需要1个参数就可以
头指针 就可以了
#include <stdio.h>
struct Node{
int data;
struct Node * pNext;
};
struct Node * CreateList(){
return pHead;
}
int main(void){
struct Node * pHead; // pHead指针 用来存放 链表头结点的地址
pHead = CreatList();
TraverseList(pHead);
return 0;
}
完整版 链表
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
struct Node{
int data; // 数据域
struct Node * pNext; // 指针域
};
struct Node * CreateList(void){
int len; // 用来存放有效节点的个数
int i;
int val; // 用来临时存放用户输入的结点的值
// 分配了一个不存放有效数据的头结点
struct Node * pHead = (struct Node *)malloc(sizeof(struct Node));
struct Node * pTail = pHead;
pTail->pNext = NULL;
printf("请输入您需要生成的链表节点的个数: len = ");
scanf("%d", &len);
for (i = 0; i < len; ++i) {
printf("请输入第%d个节点的值: ", i+1);
scanf("%d", &val);
struct Node * pNew = (struct Node *)malloc(sizeof(struct Node));
if (NULL == pNew) {
printf("分配失败,程序终止!\n");
exit(-1);
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
bool EmptyList(struct Node * pHead){
if (pHead->pNext == 0) { // pHead->pNext等价于 (*pHead).pNext
return true;
} else{
return false;
}
}
void TraverseList(struct Node * pHead){
struct Node * p = pHead->pNext;
if (EmptyList(pHead)) {
printf("链表为空!\n");
}
while (p!= NULL) {
printf("%d\n", p->data);
p = p->pNext;
}
return;
}
int main(void){
struct Node * pHead; // pHead指针 用来存放 链表头结点的地址
pHead = CreateList(); //CreatList功能:创建一个非循环 单链表,并将链表 头结点 地址 返还给pHead
TraverseList(pHead); // 把每个元素都输出
return 0;
}
总结
狭义算法:
算法是 依附于 存储结构的
不同的 存储结构,所执行的算法是不一样的!
广义算法:
广义的算法也叫泛型
无论数据是如何存储的,对该数据的操作都是一样的。
即 站在更高的维度,数组 和 链表 都是线性结构
都是 输出 第一个元素,第二个元素,第三个元素,这样。
位运算符:
& 按位与
&& 叫 逻辑与 也叫并且
| 按位或
|| 叫 逻辑或
^
>>
<<
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
int i = 5;
int j = 7;
int k;
k = i & j;
// 0101
// 0111
// 0101 是5
/*
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
*/
printf("%d\n",k); // 5
k = i && j;
printf("%d\n",k); // k的值只能是1或0,因为&&是逻辑运算符。逻辑运算符的结果只能是 真 或 假。 // 1
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void)
{
int i = 5;
int j = 7;
int k;
k = i | j;
// 0101
// 1101
// 1101 结果为7
// 1|0 = 1
// 1|1 = 1
// 0|1 = 1
// 0|0 = 0
printf("%d\n",k); // 7
k = i || j;
printf("%d\n",k); // k的值只能是1或0,因为&&是逻辑运算符。逻辑运算符的结果只能是 真 或 假。 // 1
return 0;
}
按位取反
int main(void)
{
int i = 5;
int j = 7;
int k;
k = ~i; // ~i: 把i变量所有的二进制取反
/*
i 1001
k 0110 前面补1
*/
printf("%d\n",k); // -6
k = i || j;
printf("%d\n",k); // k的值只能是1或0,因为&&是逻辑运算符。逻辑运算符的结果只能是 真 或 假。 // 1
return 0;
}
int main(void)
{
int i = 5;
int j = 7;
int k;
k = i ^ j;
/*
相同为零
不同为1
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 1
0 ^ 0 = 0
*/
// 1001
// 1011
// 0010
printf("%d\n",k); // 2
k = i || j;
printf("%d\n",k); // k的值只能是1或0,因为&&是逻辑运算符。逻辑运算符的结果只能是 真 或 假。 // 1
return 0;
}
int main(void)
{
int i = 5;
int j = 7;
int k;
k = i << 2;
/*
i << 1 表示把i的所有二进制 往左移 一位
左移n位 相当于 乘以 2的n次方
*/
printf("%d\n",k); // 5 x 4 = 20
k = i || j;
printf("%d\n",k); // k的值只能是1或0,因为&&是逻辑运算符。逻辑运算符的结果只能是 真 或 假。 // 1
return 0;
}
左移
int main(void)
{
int i = 5;
int j = 7;
int k;
k = i << 2; // 把i的所有二进制位左移2位,右边补零
/*
i << 1 表示把i的所有二进制 往左移 一位
左移n位 相当于 乘以 2的n次方
*/
printf("%d\n",k); // 5 x 4 = 20
return 0;
}
int main(void)
{
int i = 5;
int k;
k = i >> 2; // 把i的所有二进制位右移2位,左边补零
/*
右移动 相当于 除以2的n次方
前提是数据不能 丢失
*/
printf("%d\n",k); // 5 ➗ 4 = 1
return 0;
}
位运算负的现实意义:
通过位运算符我们可以对数据的操作精确到每一位