一:简介
1: 什么是数据结构?
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
2: 什么是算法?
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
3: 什么是算法的时间复杂度?
衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,时间复杂度主要是衡量算法的运行快慢,而空间复杂度主要是衡量算法运行所需要的额外空间。
时间复杂度:并不是指具体的执行时间,因为同一个算法在不同的机器,不同的变量下执行时间是不同的,所以没办法衡量,所以通过算法中的基本操作的执行次数来衡量复杂度。
例子1:
// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
int count = 0;
for (int i = 0; i<N ;++ i)
{
for (int j= 0; j<N ;++ j)
{
++count;
}
}
for (int k = 0; k <2*N ;++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n",count);
}
分析n*n+2n+10所以时间复杂度的函数式为F(N) = N*N + 2*N +10
进一步分析:
N=10 | F(N) = 130 |
N=100 | F(N) = 10210 |
N=1000 | F(N) = 1002010 |
可以发现N越大,后两项(2*N+10)对后面的结果影响就越小
所以实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数而只需要大概执行次数,那么这里我们使用大O的渐进表示法。即上述函数的时间复杂度为O(N*N)。
什么是大O的渐进表示法?
大O符号(Big o notation): 是用于描述函数渐进行为的数学符号推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数得到的结果就是大O阶。
例子2:
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
int count = 0;
for (int k=0; k<M; ++k)
{
++count;
}
for (int k=0; k<N; ++k)
{
++count;
}
printf("%d\n", count);
}
分析:没有说明M和N的大小关系时,函数的时间复杂度为O(N+M)
一般情况下时间复杂度计算时未知数都是用的N但是也可以时M、K等等其他的O(M+N)
如果M远大于N则为O(M)
N远大于M则为O(N)
M和N差不多大->既可以为 O(M)也可以为 O(N)。
例子3:
// 计算Func4的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k=0; k<100; ++k)
{
++count;
}
printf("%d\n", count);
}
分析:用常数1取代运行时间中的所有加法常数,所以函数的时间复杂度为O(1)。
例子4:
// 计算时间复杂度?
const char*strchr(const char *str,int character)
{
while(*str !='\0')
{
if(*str==character)
return str;
else
++str;
}
return NULL;
}
另外有些算法的时间复杂度存在最好、平均和最坏情况:最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如: 在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况: N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以时间复杂度为O(N) 。
例子5:
#include<stdio.h>
//分析下列算法的时间复杂度
void BubbleSort(unsigned char * a, int n)
{
int b;
for (int end = n; end > 0; end--)
{
for (int i = 1; i < end; i++)
{
if (a[i - 1] > a[i])
{
b = a[i];
a[i] = a[i - 1];
a[i - 1] = b;
}
printf("第%d次排序", i);
for (int i = 0; i < 10; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
}
}
int main()
{
unsigned char a[] = {191,13,14,16,11,9,8,7,6,5};
BubbleSort(a, 10);
for (int i = 0; i < 10; i++)
{
printf("%d ", a[i]);
}
return 0;
}
分析该函数为冒泡函数,精确的时间复杂度为F(N) = N*(N-1)/2
所以时间复杂度为O(N^2)
例子6:
//分析下列算法的时间复杂度
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;
printf("请输入你需要查找的数字\n");
scanf("%d", &k);
int sz = sizeof(arr) / sizeof(arr[0]);
int left = 0;//左下标
int right = sz - 1;//右下标
while (left <= right)
{
int mid = (left + right) / 2;//中间元素下标;mid = left+(right-left)/2
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("找到了下标是:%d", mid);
break;
}
if (left > right)
printf("不存在该数字\n");
}
return 0;
}
分析该函数为二分查找函数,时间复杂度为O(log^2N)
例子7:
//分析下列算法的时间复杂度
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
递归算法: 递归次数*每次递归调用的次数
所以时间复杂度为O(N)
例子8:
//分析下列算法的时间复杂度
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1)* Fib(N-2);
}
根据上图分析:时间复杂度为O(2^N)
4: 什么是算法的空间复杂度?
空间复杂度也是一个数学函数表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。
空间复杂度不是程席占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
例子1:
#include<stdio.h>
//分析下列算法的空间复杂度
void BubbleSort(unsigned char * a, int n)
{
int b;
for (int end = n; end > 0; end--)
{
for (int i = 1; i < end; i++)
{
if (a[i - 1] > a[i])
{
b = a[i];
a[i] = a[i - 1];
a[i - 1] = b;
}
printf("第%d次排序", i);
for (int i = 0; i < 10; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
}
}
int main()
{
unsigned char a[] = {191,13,14,16,11,9,8,7,6,5};
BubbleSort(a, 10);
for (int i = 0; i < 10; i++)
{
printf("%d ", a[i]);
}
return 0;
}
分析冒泡排序的空间复杂度,计算其额外申请的空间,先申请end,再申请i,循环结束后销毁再申请end和i;所以只申请了2个额外的空间即空间复杂度为O(1);
例子2:
例子3:
递归的空间复杂度等于递归的深度
例子4:
斐波那契的空间复杂度为O(N),因为空间可以重复利用,不累计的,但时间是一去不复放的。
练习题:
解题思路:
方法一:使用函数排序,这样数字就和数组下标相对应,然后遍历数组nums,用i != nums[i]判断,等于表示i存在,不等于表示缺失了的那个数。
int missingNumber(int* nums, int numsSize)
{
int temp;
for (int i = 0; i < numsSize; i++)
{
for (int j = numsSize-1; j > i; j--)
{
if (nums[j] < nums[j - 1])
{
temp = nums[j];
nums[j] = nums[j - 1];
nums[j - 1] = temp;
}
}
}
for (int i = 0; i < numsSize; i++)
{
if (i != nums[i])
return i;
}
}
//复杂度为O(N^2)不满足要求
方法二:将所有0-n的数字相加减去nums数组里的值得出的结果就是缺少的数字
int miss_Number(int* nums, int n)
{
int a = 0, b = 0;
for(int i = 0; i =< n; i++)
{
a += i;
}
for(int i = 0; i < n; i++)
{
b += nums[i]
}
return a - b;
}
//时间复杂度:O(n) 空间复杂度:O(1)
方法三:建立一个新数组,初始值赋为-1,将n+1个数依次与数组中的数进行对应,剩下的即为缺失的。
int missingNumber(int* nums, int numsSize){
//开辟numsSize+1大小的数组
int* newNums = (int*)malloc(sizeof(int)*(numsSize+1));
//给数组赋值 -1
for(int i = 0;i < numsSize+1;i++){
newNums[i] = -1;
}
//给newNums数组赋nums的数字,把下标和数字对应映射起来
for(int i = 0;i <numsSize;i++)
{
newNums[nums[i]] = nums[i];
}
//找消失的数字
int i;
for(i= 0;i <numsSize+1;i++){
if(newNums[i] == -1)
break;
}
free(newNums);
newNums = NULL;
return i;
}
//时间复杂度:O(n) 空间复杂度:O(n)
方法四:数组nums里的数字和0~n数字异或。
方法四思路:
1:异或性质 0^x = x; x^x = 0; 同样的数异或两次得到零;异或满足交换律。
2:先异或[0,n]的所有数字,再异或nums数字的所有数字:
3:由于相同的数异或会得到0,并且0异或任意数结果都是任意数。
4:所以再本题中,异或两次的会被变为0,异或一次的就不会发生变化: 输入:[3,0,1] :先异或:0到n的所有数字:X ^ 1 ^ 2 ^ 3;再异或数组: X ^ 1 ^ 2^ 3 ^ 3^ 0 ^ 1 = 0^ 1^ 1^ 2 ^3 ^3 = 2; 即2是消失的数字。
int missingNumber(int* nums, int numsSize)
{
int x = 0;
for (int i = 0; i <= numsSize ; i++)
x = x ^ i;
for (int i = 0; i < numsSize; i++)
x = x ^ nums[i];
return x;
}
//时间复杂度:O(n) 空间复杂度:O(1)
OJ例题
思路一:这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
很明显暴力解法的时间复杂度是O(n^2),空间复杂度为O(1)。
int removeElement(int* nums, int numsSize, int val)
{
for(int i=0; i<numsSize; i++)
{
if(nums[i] != val)
{
for(int a = i; a<numsSize-1; a++)
{
nums[a] = nums[a+1];
}
i--;
numsSize--;
}
}
return numsSize;
}
思路二:遍历数组,如果发现语vel不同的就创建一个新的数组去接收,最后将新数组赋值给原来的数组
时间复杂度是O(n),空间复杂度为O(n)。
int removeElement(int* nums, int numsSize, int val)
{
int *newnums = (int*)malloc(sizeof(int)*(numsSize+1));
int newsSize = 0;
for(int i=0; i<numsSize; i++)
{
if(nums[i] != val)
{
newnums[newsSize]= nums[i];
newsSize++;
}
}
for(int i = 0; i <newsSize; i++)
{
nums[i] = newnums[i];
}
return newsSize;
}
思路三:在思路二的基础上优化,不需要创建新的数组接收而是在原有的数组中存放
时间复杂度是O(n),空间复杂度为O(1)。
int removeElement(int* nums, int numsSize, int val)
{
int newSize = 0;
for(int i=0; i<numsSize; i++)
{
if(nums[i] != val)
{
nums[newSize]= nums[i];
newSize++;
}
}
return newSize;
}
5: 什么是线性表?
线性表 (inear ist)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表就是数组,但是再数组的基础上,它还要求数据是从头开始连续存储的,不能跳跃间隔
静态顺序表
#pragme once
#define N 1000
typedef int SLDataType;
//静态顺序表--特点如果满了就不让插入了,缺点N给大了浪费,N给小了不够用
typedef struct
{
SLDataType a[N];
int size;//表示数组中储存了多少个数据
}SL;
//接口函数 --建议命名风格跟着STL走
void SepListInit(SL*ps);//初始化函数
void SeqListPushBack(SL*ps, SLDateType x);//尾差函数
void SeqListPopBack(SL*ps);//尾删函数
void SeqListPushFront(SL*ps, SLDateType x);//头插函数
void SeqListPopFront(SL*ps);//头删函数
动态顺序表
#pragme once
#include <stdio.h>
#include <stdlib.h>
#include<assert.h>
typedef int SLDataType;
//动态顺序表--特点是如果满了可以动态增加
typedef struct
{
SLDataType* a;
int size;//表示数组中储存了多少个数据
int capacity;//数组实际能存的数据空间大小
}SL;
//接口函数 --建议命名风格跟着STL走
void SepListInit(SL*ps); //初始化函数
void SepListCheckCapacity(&ps); //判断空间是否够用,是否需要增容
void SeqListPushBack(SL*ps, SLDateType x); //尾差函数
void SeqListPopBack(SL*ps); //尾删函数
void SeqListPushFront(SL*ps, SLDateType x); //头插函数
void SeqListPopFront(SL*ps); //头删函数
void SepListPrtintf(SL*ps); //打印数据内容
void SepListDestroy(SL* ps); //删除顺序表
int SepListFind(SL* ps, SLDataType x); //找到了返回x的位置下标, 没找到返回-1
void SepListInsert(SL* ps, int pos, SLDataType x); //在指定下标添加数据
void SepListErase(SL*ps, int pos); //删除指定下标的数据
//初始化函数
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//判断空间是否够用,是否需要增容
void SeqListCheckCapacity(SL*ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(1);
}
ps->capacity = newcapacity;
ps->a = tmp;
}
}
//尾插函数
void SeqListPushBack(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//尾删函数
void SeqListPopBack(SL* ps)
{
//温柔的处理方式
//if(ps->size>0)
// {
// ps->a[ps->size-1] = 0;
// ps->size--;
//}
assert(ps->size > 0);
ps->size--;
}
//头插函数
void SeqListPushFront(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps);
for (int i = ps->size - 1; i >= 0; i--)
{
ps->a[i + 1] = ps->a[i];
}
ps->size++;
ps->a[0] = x;
}
//头删函数
void SeqListPopFront(SL* ps)
{
//assert(ps->size > 0);//粗暴的模式
if (ps->size > 0)
{
for (int i = 0; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
}
//找到了返回x的位置下标, 没找到返回-1
int SeqListFind(SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
//在指定下标添加数据
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(pos <= ps->size && pos >= 0);//判断pos是否大于size以及是否小于0
SeqListCheckCapacity(ps);//判断空间是否够用
for (int i = ps->size - 1; i >= pos; i--)
{
ps->a[i + 1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
//删除指定下标的数据
void SeqListErase(SL* ps, int pos)
{
assert(pos < ps->size || ps >= 0);//判断pos是否大于size以及是否小于0
for (int i = pos; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//打印数据内容
void SeqListPrtintf(SL* ps)
{
for (int i = 0; i < ps->size; i++)
printf("%d ", ps->a[i]);
printf("\n");
}
//删除顺序表
void SeqListDestroy(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
void Menu()
{
printf("***********************************************************************\n");
printf("请输入你的操作:\n");
printf("1.头插 2.头删\n");
printf("3.尾插 4.尾删\n");
printf("5.打印 6.指定下标添加数据\n");
printf("7.删除指定下标的数据 8.找到了指定数据位置的下标\n");
printf("-1.退出\n");
printf("***********************************************************************\n");
}
void MenuTest(void)
{
SL sl;//创建顺序表
SeqListInit(&sl);
int input = 0;
int x = 0;
int pos = 0;
while (input != -1)
{
Menu();
scanf("%d" ,& input);
switch (input)
{
case 1:
{
printf("请输入需要头插的数据以-1结束\n");
scanf("%d", &x);
while (x != -1)
{
SeqListPushFront(&sl, x);
scanf("%d", &x);
}
break;
}
case 2:
{
SeqListPopFront(&sl);
break;
}
case 3:
{
printf("请输入需要尾插的数据以-1结束\n");
scanf("%d", &x);
while (x != -1)
{
SeqListPushBack(&sl, x);
scanf("%d", &x);
}
break;
}
case 4:
{
SeqListPopBack(&sl);
break;
}
case 5:
{
SeqListPrtintf(&sl);
break;
}
case 6:
{
printf("请输入需要插入数据的下标\n");
scanf("%d", &pos);
printf("请输入需要插入的数据\n");
scanf("%d", &x);
SeqListInsert(&sl, pos, x);
break;
}
case 7:
{
printf("请输入需要删除数据的下标\n");
scanf("%d", &pos);
SeqListErase(&sl, pos);
break;
}
case 8:
{
printf("请输入需要查找的数据\n");
scanf("%d", &x);
int location = SeqListFind(&sl, x);
if(location != -1)
printf("数据位于第%d个下标\n", location);
else
printf("无此数据\n");
break;
}
case -1:
break;
default:
printf("无此选项请重新输入\n");
}
}
SeqListDestroy(&sl);
}
int main()
{
MenuTest();
}
顺序表缺陷:
1:空间不够了需要扩容,增容是需要付出代价的,因为如果原空间不足,会在原地或者原地空间不够用在异地开辟新空间,而在异地开辟新空间是需要拷贝数据删除原数据的。
2:为了避免频繁扩容,空间不够都是原空间的2倍大小扩容,容易浪费空间
3:顺序表要求数据从开始位置连续存储,那么我们在头部或者中间位置插入数据就需要挪动数据,效率不高。
单链表
链表是针对顺序表的缺陷设计的
优点:
1:按需申请空间,不用了就释放孔空间
2:头部中间插入数据不需要挪动空间
3:不存在空间浪费
4:支持随机访问(有些算法需要支持随机访问,比如二分查找,比如优化快排等)
单链表是一种链式存储的结构。它动态的为节点分配存储单元。当有节点插入时,系统动态的为结点分配空间。在结点删除时,应该及时释放相应的存储单元,以防止内存泄露。由于是链式存储,所以操作单链表时,必须知道头结点或者头指针的位置。并且,在查找第i个节点时,必须找到第i-1个节点。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLLDataType;
typedef struct SLLstruct
{
SLLDataType Data;
struct SLLstruct* Next;
}SLinkList;
SLinkList* SLLCreateNewNode(SLLDataType x); //创建新的单链表节点
void SLListprintf(SLinkList* phead); //打印函数
void SLLPushBack(SLinkList** phead, SLLDataType x); //尾插函数
void SLLPushFront(SLinkList** phead, SLLDataType x); //头插函数
void SLLPopBack(SLinkList** phead); //尾删函数
void SLLPopFront(SLinkList** phead); //头删函数
SLinkList* SLLFind(SLinkList* phead, SLLDataType x); //查找指定数据的地址
void SLLInsertFront(SLinkList** phead, SLinkList* pos, SLLDataType x); //在pos位置之前插入一个节点
void SLLInsertAfter(SLinkList* pos, SLLDataType x); //在pos位置之后插入一个节点
void SLLErase(SLinkList** phead, SLinkList* pos); //删除单链表的指定数据
void SLLEraseAfter(SLinkList* pos); //删除单链表的指定数据之后的数据
void SLLDestroy(SLinkList** phead); //删除整个链表
//创建新的单链表节点
SLinkList* SLLCreateNewNode(SLLDataType x)
{
SLinkList* NewNode = (SLinkList*)malloc(sizeof(SLinkList));
if (NewNode == NULL)
{
printf("malloc fail");
exit(1);
}
else
{
NewNode->Data = x;
NewNode->Next = NULL;
return NewNode;
}
}
//打印函数
void SLListprintf(SLinkList* phead)
{
SLinkList* cur = phead;
while (cur != NULL)
{
printf("%d->",cur->Data);
cur = cur->Next;
}
printf("NULL\n");
}
//尾插函数
void SLLPushBack(SLinkList** phead, SLLDataType x)
{
SLinkList* LastData = SLLCreateNewNode(x);
LastData->Next = NULL;
if (*phead == NULL)
{
*phead = LastData;
}
else
{
SLinkList* tail = *phead;
while (tail->Next != NULL)
{
tail = tail->Next;
}
tail->Next = LastData;
}
}
//头插函数
void SLLPushFront(SLinkList** phead, SLLDataType x)
{
SLinkList* FrontData = SLLCreateNewNode(x);
FrontData->Next = *phead;
*phead = FrontData;
}
//尾删函数
void SLLPopBack(SLinkList** phead)
{
//单链表里没有数据
if (*phead == NULL)//粗暴的方式 assert(*phead != NULL);
return;
//单链表里只有一个数据
if ((*phead)->Next == NULL)
{
free(*phead);
*phead = NULL;
}
//单链表里有2个及以上数据
SLinkList* tail = *phead;
while (tail->Next->Next != NULL)
{
tail = tail->Next;
}
free(tail->Next);
tail->Next = NULL;
}
//头删函数
void SLLPopFront(SLinkList** phead)
{
assert(*phead);
SLinkList* next = (*phead)->Next;
free(*phead);
*phead = next;
}
//查找指定数据的地址
SLinkList* SLLFind(SLinkList* phead, SLLDataType x)
{
SLinkList* cur = phead;
while (cur != NULL && cur->Data != x)
{
cur = cur->Next;
}
return cur;
}
//在pos位置之前插入一个节点
void SLLInsertFront(SLinkList** phead, SLinkList* pos, SLLDataType x)
{
SLinkList* newnode = SLLCreateNewNode(x);
if (*phead == pos)
{
*phead = newnode;
newnode->Next = pos;
}
else
{
SLinkList* prevnode = *phead;
while (prevnode->Next != pos)//找到pos的前一个节点
{
prevnode = prevnode->Next;
}
prevnode->Next = newnode;
newnode->Next = pos;
}
}
//在pos位置之后插入一个节点
void SLLInsertAfter(SLinkList* pos, SLLDataType x)
{
SLinkList* newnode = SLLCreateNewNode(x);
newnode->Next = pos->Next;
pos->Next = newnode;
}
//删除单链表的指定数据
void SLLErase(SLinkList** phead, SLinkList* pos)
{
if ( *phead == pos )
{
phead = pos->Next;
free(pos);
}
else
{
SLinkList* prev = *phead;
while (prev->Next != pos)
{
prev =prev->Next;
}
prev->Next = pos->Next;
free(pos);
}
}
//删除单链表的指定数据之后的数据
void SLLEraseAfter(SLinkList* pos)
{
assert(pos->Next);
SLinkList* nextnode = pos->Next;
pos->Next = nextnode->Next;
free(nextnode);
}
//删除整个链表
void SLLDestroy(SLinkList** phead)
{
SLinkList* cur = *phead;
while (cur)
{
SLinkList* nextnode = cur->Next;
free(cur);
cur = nextnode->Next;
}
*phead = NULL;
}
void main()
{
SLinkList* plist = NULL;
SLLPushBack(&plist,2);
//SLLPushBack(&plist, 1);
SLLPushFront(&plist,3);
SLLPushFront(&plist, 4);
SLLPushFront(&plist, 1);
//SLLPopBack(&plist);
//SLLPopFront(& plist);
//SLLPopFront(&plist);
//SLLPopFront(&plist);
SLinkList* a = SLLFind(plist, 1);
if (a)
{
SLLEraseAfter(a);
}
SLListprintf(plist);
}
单纯的单链表是没有意义的,因为单链表的缺陷还是很多,单链表的增删查找的意义不大
OJ题1:
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* cur = head;
struct ListNode* prev = NULL;
while(cur != NULL)
{
if(cur->val == val)
{
if(cur == head)
{
head = cur->next;
free(cur);
cur = head;
}
else
{
prev->next = cur->next;
free(cur);
cur = prev->next;
}
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
OJ题2:
分析:
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* cur = head;
struct ListNode* copy = NULL;
struct ListNode* prev = NULL;
while(cur)
{
copy = cur->next;//拷贝当前节点指向的下一个节点
cur->next = prev;//将当前节点指向的下一个节点改为上一个节点
prev = cur;//储存当前节点给下一次循环使用
cur = copy;//将当前节点指向拷贝的下一个节点,用于循环判断
}
return prev;
}
OJ题3:
分析:双指针第一指针指向第倒是第k个节点,第二个指针是第一个指针+k个节点,同步移动,当移动到最后第一个指针就是结果。
struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
struct ListNode* cur = head;
struct ListNode* cur_n_k = head;
while(k--)
{
cur = cur->next;
}
while(cur)
{
cur = cur->next;
cur_n_k = cur_n_k->next;
}
return cur_n_k;
}
OJ题4:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
struct ListNode* newlist_head = NULL;
struct ListNode* newlist_tail = NULL;
if(list1 == NULL)
return list2;
if(list2 == NULL)
return list1;
while(list1 && list2)
{
if(list1->val < list2->val)
{
if(newlist_head == NULL)
{
newlist_head = newlist_tail = list1;
}
else
{
newlist_tail->next = list1;
newlist_tail = newlist_tail->next;
}
list1 = list1->next;
}
else
{
if(newlist_head == NULL)
{
newlist_head = newlist_tail = list2;
}
else
{
newlist_tail->next = list2;
newlist_tail = newlist_tail->next;
}
list2 = list2->next;
}
}
if(list1)
{
newlist_tail->next = list1;
}
if(list2)
{
newlist_tail->next = list2;
}
return newlist_head;
}
OJ题5:
分析不创建哨兵位方法:
struct ListNode* partition(struct ListNode* head, int x)
{
struct ListNode*lesshead = NULL,*lesstail= NULL,*greaterhead= NULL,*greatertail= NULL;
if(head == NULL)
return lesshead;
while(head)
{
if(head->val<x)
{
if(lesshead == NULL)
lesshead = lesstail = head;
else
{
lesstail->next = head;
lesstail = lesstail->next;
}
head = head->next;
}
else
{
if(greaterhead == NULL)
greaterhead = greatertail = head;
else
{
greatertail->next = head;
greatertail = greatertail->next;
}
head = head->next;
}
}
if(greatertail != NULL)
greatertail->next = NULL;
if(lesshead != NULL)
{
lesstail->next = greaterhead;
return lesshead;
}
else
return greaterhead;
}
创建哨兵位方法:
struct ListNode* partition(struct ListNode* head, int x)
{
struct ListNode *lesshead, *lesstail,*greaterhead,*greatertail;
lesshead = lesstail = (struct ListNode*)malloc(sizeof(struct ListNode));
lesstail->next = NULL;
greaterhead = greatertail = (struct ListNode*)malloc(sizeof(struct ListNode));
greatertail->next = NULL;
struct ListNode *cur = head;
while(cur)
{
if(cur->val<x)
{
lesstail->next = cur;
lesstail = lesstail->next;
}
else
{
greatertail->next = cur;
greatertail = greatertail->next;
}
cur = cur->next;
}
greatertail->next = NULL;
lesstail->next = greaterhead->next;
head = lesshead->next;
free(lesshead);
free(greaterhead);
return head;
}
OJ题5: