データ構造の救世主[時間計算量、空間計算量--1]

コンテンツ

データ構造の序文

1.アルゴリズムの複雑さ

2.時間計算量

2.1時間計算量の概念

2.2BigOの漸近表記

2.3時間計算量には、最良、平均、および最悪のケースがあります

2.4一般的な時間計算量の計算例

3.スペースの複雑さ

注:時間は蓄積され(永久に失われます)、スペースは蓄積されません(再利用可能)

4.一般的な時間計算量と複雑さoj演習


 

データ構造の序文

データ構造とは何ですか?

データ構造は、コンピューターがデータを格納および整理するための方法であり、相互に1つ以上の特定の関係を持つデータ要素のコレクションを指します。

アルゴリズムとは何ですか?

アルゴリズム:1つまたは一連の値を入力として受け取り、1つまたは一連の値を出力として生成する明確に定義された計算プロセス。簡単に言えば、アルゴリズムは、入力データを出力結果に変換するために使用される一連の計算ステップです。


1.アルゴリズムの複雑さ

アルゴリズムが実行可能プログラムに書き込まれた後、実行には時間リソースとスペース(メモリ)リソースが必要です。したがって、アルゴリズムの品質を測定するには、通常、時間と空間の2つの次元、つまり時間の複雑さと空間の複雑さから測定されます。

時間計算量は主にアルゴリズムの実行速度を測定し、空間計算量は主にアルゴリズムの実行に必要な余分なスペースを測定します。コンピュータ開発の初期には、コンピュータのストレージ容量は非常に小さかった。そのため、スペースの複雑さが非常に懸念されます。しかし、コンピュータ産業の急速な発展の後、コンピュータのストレージ容量は非常に高いレベルに達しました。したがって、アルゴリズムのスペースの複雑さに特別な注意を払う必要はなくなりました。


2.時間計算量

2.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 = 10 F(N)= 130
N = 100 F(N)= 10210
N = 1000 F(N)= 1002010
N = 10000 F(N)= 100,020,010

Nが大きいほど、結果に対する2 * N + 10の影響が小さくなり、N^2が結果の大部分を占めることがわかります。F(N)を使用して時間計算量を表す場合、各アルゴリズムは対応する関数を慎重に計算する必要がありますが、これは非常に複雑です。実際には、時間計算量を計算するときに、必ずしも正確な実行回数を計算する必要はありません。 、ただし、必要な実行回数はおおよその数(推定次数)のみであるため、大きなOの漸近表記を使用します。


2.2BigOの漸近表記

Big O表記法:関数の漸近的振る舞いを説明するために使用される数学表記法です。

big-Oメソッドを導出します。

1.実行時のすべての加法定数を定数1に置き換えます

2.変更された実行時間関数では、最上位の項のみが保持されます

3.最上位の項が存在し、1でない場合は、この項を掛けた定数を削除します。結果は大きなOオーダーです

大きなOの漸近表現を使用した後、Func1の時間計算量は次のようになります。O(N ^ 2)

N = 10 F(N)= 100
N = 100 F(N)= 10,000
N = 1000 F(N)= 1,000,000
N = 10000 F(N)= 100,000,000

結果にほとんど影響を与えない項目を削除し、実行回数を簡潔かつ明確に表示します

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);
}

 回答:O(M + N)、通常の状況では、Nは不明として使用されますが、M + Nはサイズ(不明)を認識していないため、MとNの両方を残す必要があります

NがMよりもはるかに大きいと記載されている場合はO(N)、MがNよりもはるかに大きい場合はO(M)、NがMと同じ場合はO(N)またはO(M ) に使える


2.3時間計算量には、最良、平均、および最悪のケースがあります

最悪の場合:任意の入力サイズの最大実行数(上限)

平均的なケース:任意の入力サイズで必要な実行回数

最良の場合:任意の入力サイズの最小実行数(下限)

例:長さNの配列でデータを検索xベストケース:1検索ワーストケース:N検索平均ケース:N/2検索

実際には、一般的な懸念事項はアルゴリズムの最悪の場合の操作であるため、配列内のデータを検索する時間計算量はO(N)です。


2.4一般的な時間計算量の計算例

Func2の時間計算量を計算しますか?   

void Func2(int N)
{
 int count = 0;
 for (int k = 0; k < 2 * N ; ++ k)
 {
 ++count;
 }
 int M = 10;
 while (M--)
 {
 ++count;
 }
 printf("%d\n", count);
}

答案:O(N)

大きなO次規則:2。変更された実行時間関数で、最上位の項のみを保持します。3.最上位の項が存在し、1でない場合は、この項を掛けた定数を削除します。結果はbig-O注文です。Nが無限大になる傾向がある場合、Nは桁を表し(億万長者、1億、2億は依然として億万長者です)、+ 10はほとんど影響を与えず、同時に係数が削除されます。

Func4の時間計算量を計算しますか?

void Func4(int N)
{
 int count = 0;
 for (int k = 0; k < 100; ++ k)
 {
 ++count;
 }
 printf("%d\n", count);
}

回答:O(1)

一定である限り、時間計算量は1に置き換えられます。Big-O-orderルール:1。実行時のすべての加法定数を定数1に置き換えます

strchrの計算の時間計算量?

const char * strchr ( const char * str, int character );//在字符串中查找一个字符

回答:O(N)、最良の基本操作は1回実行され、最悪はN回、時間計算量は一般に最悪であり、時間計算量はO(N)です。


バブルソートの時間計算量を計算しますか?

void BubbleSort(int* a, int n)
{
 assert(a);
 for (size_t end = n; end > 0; --end)
 {
 int exchange = 0;
 for (size_t i = 1; i < end; ++i)
 {
 if (a[i-1] > a[i])
 {
 Swap(&a[i-1], &a[i]);
 exchange = 1;
 }
 }
 if (exchange == 0)
 break;
 }
}

答案:O(N^2)

計算時間計算量は、必ずしも正確ではない数値ループを取ることはできません。アルゴリズムの考え方に従って計算する必要があります。

バブルソートは、正確な回数であるN-1回を比較します。F(N)= N-1 + N-2 + N-3 + ... 2 + 1基本的な操作はN回実行するのが最適で、最悪の場合は実行します。実行されます(N *(N-1)/ 2回(等差数列)、大き​​なO次の方法を導出することによって+時間計算量は一般に最悪、時間計算量はO(N ^ 2)、最良の場合はO (N)


BinarySearch(バイナリ検索)の時間計算量を計算しますか?

int BinarySearch(int* a, int n, int x)
{
 assert(a);
 int begin = 0;
 int end = n-1;
 // [begin, end]:begin和end是左闭右闭区间,因此有=号
 while (begin <= end)
 {
 int mid = begin + ((end-begin)>>1);
 if (a[mid] < x)
 begin = mid+1;
 else if (a[mid] > x)
 end = mid-1;
 else
 return mid;
 }
 return -1;
}

 回答:O(logN)

基本的な操作は、O(1)を実行すると最適に実行されます。最悪の場合、最後の番号が見つかったときに、それが見つからないか、検索する番号が1つしか残っていない場合、×2、x2、最後に元の配列が取得されます。 。同様に、半分に折りたたまれた回数半分の検索がx回であると仮定すると、2を2で割るだけです。2^ x = N

テキストに対数を書くのは簡単ではないので、logNと省略します。これは、アルゴリズム分析では、底が2で、対数がNであることを意味します。いくつかの場所では、それはlgNとして書かれます(数学では、lgNは10に基づいています、この書き方は曖昧です、logNとして書くのが最善です)


階乗再帰Facの計算の時間計算量?

long long Fac(size_t N)
{
 if(0 == N)
 return 1;
 
 return Fac(N-1)*N;
}

答案:O()N)

計算と分析により、再帰は合計でN + 1回、内部再帰は一定時間、Fac(N)Fac(N-1)Fac(N-2)...時間の複雑さはOであることがわかります。 (N)。


フィボナッチ再帰Fibの計算の時間計算量?

long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

答案:O(2^N)

比例シーケンス、2 ^ 0 + 2 ^ 1 + ... + 2 ^(N-2)= 2 ^(N-1)-1  

 


 

3.スペースの複雑さ

スペースの複雑さも数式であり、アルゴリズムの操作中に一時的に占有されるストレージスペースの量の尺度です。

スペースの複雑さは、プログラムが占めるバイト数ではありません。これはあまり意味がないため、スペースの複雑さは変数の数です。スペースの複雑さの計算規則は、基本的に実際の複雑さと同様であり、big-O漸近表記も使用します。

注:関数の実行に必要なスタックスペース(ストレージパラメーター、ローカル変数、一部のレジスタ情報など)はコンパイル時に決定されるため、スペースの複雑さは主に、実行時に関数によって明示的に要求される追加スペースによって決定されます。

注:時間は蓄積され(永久に失われます)、スペースは蓄積されません(再利用可能)


バブルソートのスペースの複雑さを計算しますか?

void BubbleSort(int* a, int n)
{
assert(a);
 for (size_t end = n; end > 0; --end)
 {
 int exchange = 0;
 for (size_t i = 1; i < end; ++i)
 {
 if (a[i-1] > a[i])
 {
 Swap(&a[i-1], &a[i]);
 exchange = 1;
 }
 }
 if (exchange == 0)
 break;
 }
}

回答:O(1)

一定量の余分なスペースが使用されるため、スペースの複雑さはO(1)です。提供されたN個の数値の配列は、アルゴリズムに含まれていません。スペースの複雑さは、このアルゴリズムが開いているためにのみ計算されます。4つしかありません。一時変数はアルゴリズムのために作成されます。交換は破棄されますが、元のスペースは引き続き使用されます。


フィボナッチの空間の複雑さを計算しますか?

long long* Fibonacci(size_t n)
{
 if(n==0)
 return NULL;
 
 long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
 fibArray[0] = 0;
 fibArray[1] = 1;
 for (int i = 2; i <= n ; ++i)
 {
 fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
 }
 return fibArray;
}

回答:スペースの複雑さはO(N)です

mallocは、アルゴリズムのためにn+1個のスペースを開きます


階乗再帰Facを計算するスペースの複雑さ?

long long Fac(size_t N)
{
 if(N == 0)
 return 1;
 
 return Fac(N-1)*N;
}

回答:スペースの複雑さはO(N)です

スタックフレームを開き、合計n + 1スタックフレームを開きます。各スタックフレームはO(1)です。


フィボナッチ再帰フィブの空間の複雑さを計算しますか?

long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

回答:スペースの複雑さはO(N)です

関数は最初にFib(N)Fib(N-1)Fib(N-2)..を計算し、次に右に移動して計算します。関数は下向きに再帰し、関数は戻った後に破棄され、スペースの複雑さは次のようになります。オン)

void test1()
{
	int a = 0;
	printf("%p\n", &a);
}
void test2()
{
	int b = 0;
	printf("%p\n", &b);
}
int main()
{
	test1();
	test2();
	return 0;
}

アドレスが同じであることがわかります。詳細については、私の関数スタックフレームの記事C言語の救世主の追加記事(関数スタックフレームの作成と破棄)を参照してください-プログラマーが探していました

 


4.一般的な時間計算量と複雑さoj演習

114514 O(1) 一定の順序
3n + 4 オン) 線形順序
3n ^ 2 + 4n + 5 O(N ^ 2) スクエアオーダー
3log(2)n + 4 O(logN) 対数
2n + 3nlog(2)n + 14 O(nlogN) nlogn注文
n ^ 3 + 2n ^ 2 + 4n + 6 O(N ^ 3) 立方体の順序
2 ^ n O(2 ^ N) 指数関数的な順序

 4.1 Leetcode:配列にはからまでのnumsすべての整数が含まれていますが、1つが欠落しています。その欠落している整数を見つけるためのコードを書いてください。O(n)時間でそれを行う方法はありますか?0n

 アイデア:

O(N)時間計算量で完了する必要があり、XORの特性を使用して完了することができます。2つの同じ数のXORは一緒に0です。内容が同じである限り、XORは順序を気にせず、欠落している番号と対応する配列をXORにするだけです。

int missingNumber(int* nums, int numsSize){
  int x=0;

  for(int i=0;i<numsSize;i++)
  {
   x ^= nums[i];
  }
  for(int j=0;j<numsSize+1;j++)
  {
   x^=j;
  }
  return x;
}

 アレイを回転させる 

アイデア:

 通常の方法を使用する場合、時間計算量は少なくともO(N)であり、配列を1回トラバースする場合、この問題から抜け出すために使用できるのは3ステップのトス方法のみです(ほとんど考えられません)。

void reverse(int*nums,int left,int right)
{
    while(left<right)
    {
        int tmp=nums[left];
        nums[left]=nums[right];
        nums[right]=tmp;
        --right;
        ++left;
    }
}


void rotate(int* nums, int numsSize, int k){
    k%=numsSize;    
    reverse(nums,numsSize-k,numsSize-1);
    reverse(nums,0,numsSize-k-1);
    reverse(nums,0,numsSize-1);

}

おすすめ

転載: blog.csdn.net/weixin_63543274/article/details/124237885